初识go的tomb包

在分析github.com/hpcloud/tail 这个包的源码的时候,发现这个包里用于了一个另一个包,本身也没有用过,可是这个包在tail这个包里又起来很是大的做用git

当时并无彻底弄明白这个包的用法和做用,因此又花时间找了这个包的使用和相关文档,其中看了https://blog.labix.org/2011/10/09/death-of-goroutines-under-control 这篇文章整理的挺好的,本身对这个文章进行了简单的翻译,下面这个文章中的使用是gopkg.in/tomb.v2github

Death of goroutines under control

不少人被go语言吸引的缘由是其很是好的并发性,以及channel, 轻量级线程(goroutine)等这些特性golang

而且你也会常常在社区或者其余文章里看到这样一句话:算法

Do not communicate by sharing memory;数据库

instead, share memory by communicating.网络

这个模型很是合理,以这种方式处理问题,在设计算法时会产生显著差别,但这个并非什么新闻并发

What I address in this post is an open aspect we have today in Go related to this design: the termination of background activity.(不知道怎么翻译了)ide

做为一个例子,咱们构建一个简单的goroutine,经过一个channel 发送函数

 

type LineReader struct {
        Ch chan string
        r  *bufio.Reader
}

func NewLineReader(r io.Reader) *LineReader {
        lr := &LineReader{
                Ch: make(chan string),
                r:  bufio.NewReader(r),
        }
        go lr.loop()
        return lr
}

The type has a channel where the client can consume lines from, and an internal buffer
used to produce the lines efficiently. Then, we have a function that creates an initialized
reader, fires the reading loop, and returns. Nothing surprising there.oop

接着看看loop方法

func (lr *LineReader) loop() {
        for {
                line, err := lr.r.ReadSlice('\n')
                if err != nil {
                        close(lr.Ch)
                        return
                }
                lr.Ch <- string(line)
        }
}

在这个loop中,咱们将从从缓冲器中获取每行的内容,若是出现错误时关闭通道并中止,不然则将读取的一行内容放到channel中

也许channel接受的那一方忙于其余处理,而致使其会阻塞。 这个简单的例子对于不少go开发者应该很是熟悉了

 

但这里有两个与终止逻辑相关的细节:首先错误信息被丢弃,而后没法经过一种更加干净的方式从外部终端程序

固然,错误很容易记录下来,可是若是咱们想要将它存储数据库中,或者经过网络发送它,或者甚至考虑到它的性质,在很过状况下

干净的中止也是很是有价值的

这并不是是一个很是难以作到的事情,可是今天没有简单一致的方法来处理,或者也许没有,而go中的Tom包就是试图解决这个问题的

 

这个模型很简单:tomb跟踪一个或者多个goroutines是活着的,仍是已经死了,以及死亡的缘由

为了理解这个模型,咱们把上面的LineReader例子进行改写:

type LineReader struct {
        Ch chan string
        r  *bufio.Reader
        t  tomb.Tomb
}

func NewLineReader(r io.Reader) *LineReader {
        lr := &LineReader{
                Ch: make(chan string),
                r:  bufio.NewReader(r),
        }
        lr.t.Go(lr.loop)
        return lr
}

这里有一些有趣的点:

首先,如今出现的错误结果与任何可能失败的go函数或者方法同样。

hen, the previously loose error is now returned, 标记这个goroutine终止的缘由,

最后这个通道的发送被调增,以便于无论goroutine由于上面缘由死亡都不会阻塞

A Tomb has both Dying and Dead channels returned by the respective methods, which are closed when the Tomb state changes accordingly. These channels enable explicit blocking until the state changes, and also to selectively unblock select statements in those cases, as done above.

 

经过上面的说的,咱们能够简单的引入Stop方法从外部同步请求清楚goroutine

func (lr *LineReader) Stop() error {
        lr.t.Kill(nil)
        return lr.t.Wait()
}

在这种状况下,Kill方法会将正在运行的goroutine从外部将其置于一个死亡状态,而且Wait将阻塞直到goroutine经过

本身终止返回。 即便因为内部错误,groutine已经死亡或者处于死亡状态,此过程也会正常运行,由于只有第一次用一个实际的错误调用Kill被记录为goroutine死亡缘由。 

The nil value provided to t.Kill is used as a reason when terminating cleanly without an actual error, and it causes Wait to return nil once the goroutine terminates, flagging a clean stop per common Go idioms.

 

关于gopkg.in/tomb.v2的官网说明的一段话:

The tomb package handles clean goroutine tracking and termination.

The zero value of a Tomb is ready to handle the creation of a tracked goroutine via its Go method, and then any tracked goroutine may call the Go method again to create additional tracked goroutines at any point.

If any of the tracked goroutines returns a non-nil error, or the Kill or Killf method is called by any goroutine in the system (tracked or not), the tomb Err is set, Alive is set to false, and the Dying channel is closed to flag that all tracked goroutines are supposed to willingly terminate as soon as possible.

Once all tracked goroutines terminate, the Dead channel is closed, and Wait unblocks and returns the first non-nil error presented to the tomb via a result or an explicit Kill or Killf method call, or nil if there were no errors.

It is okay to create further goroutines via the Go method while the tomb is in a dying state. The final dead state is only reached once all tracked goroutines terminate, at which point calling the Go method again will cause a runtime panic.

Tracked functions and methods that are still running while the tomb is in dying state may choose to return ErrDying as their error value. This preserves the well established non-nil error convention, but is understood by the tomb as a clean termination. The Err and Wait methods will still return nil if all observed errors were either nil or ErrDying.

 

关于gopkg.in/tomb.v1使用例子

在golang官网上看到了这样一个例子,以为用的挺好的就放这里

package main

import (
    "gopkg.in/tomb.v1"
    "log"
    "sync"
    "time"
)

type foo struct {
    tomb tomb.Tomb
    wg   sync.WaitGroup
}

func (f *foo) task(id int) {
    for i := 0; i < 10; i++ {
        select {
        case <-time.After(1e9):
            log.Printf("task %d tick\n", id)
        case <-f.tomb.Dying():
            log.Printf("task %d stopping\n", id)
            f.wg.Done()
            return
        }
    }
}

func (f *foo) Run() {
    f.wg.Add(10)
    for i := 0; i < 10; i++ {
        go f.task(i)
    }
    go func() {
        f.wg.Wait()
        f.tomb.Done()
    }()
}

func (f *foo) Stop() error {
    f.tomb.Kill(nil)
    return f.tomb.Wait()
}

func main() {
    var f foo
    f.Run()
    time.Sleep(3.5e9)
    log.Printf("calling stop\n")
    f.Stop()
    log.Printf("all done\n")
}

在关于tomb这个包的说明上,说的也很是清楚,tomb包用于追踪一个goroutine的声明周期,如:as alive,dying or dead and the reason for its death

关于v1 版本官网的说明

The tomb package offers a conventional API for clean goroutine termination.

A Tomb tracks the lifecycle of a goroutine as alive, dying or dead, and the reason for its death.

The zero value of a Tomb assumes that a goroutine is about to be created or already alive. Once Kill or Killf is called with an argument that informs the reason for death, the goroutine is in a dying state and is expected to terminate soon. Right before the goroutine function or method returns, Done must be called to inform that the goroutine is indeed dead and about to stop running.

A Tomb exposes Dying and Dead channels. These channels are closed when the Tomb state changes in the respective way. They enable explicit blocking until the state changes, and also to selectively unblock select statements accordingly.

When the tomb state changes to dying and there's still logic going on within the goroutine, nested functions and methods may choose to return ErrDying as their error value, as this error won't alter the tomb state if provided to the Kill method. This is a convenient way to follow standard Go practices in the context of a dying tomb..

 

小结

能够从上面的文章以及使用例子上看出,tomb包是一个很是实用的一个包,后面会继续整理一下关于tomb v1版本的源码,看看人家是如何实现的,学习学习

相关文章
相关标签/搜索