Goroutine

goroutine不同于thread,threads是操作系统中的对于一个独立运行实例的描述,不同操作系统,对于thread的实现也不尽相同;但是,操作系统并不知道goroutine的存在,goroutine的调度是有Golang运行时进行管理的。启动thread虽然比process所需的资源要少,但是多个thread之间的上下文切换仍然是需要大量的工作的(寄存器/Program Count/Stack Pointer/...),Golang有自己的调度器,许多goroutine的数据都是共享的,因此goroutine之间的切换会快很多,启动goroutine所耗费的资源也很少,一个Golang程序同时存在几百个goroutine是很正常的。

channel,即“管道”,是用来传递数据(叫消息更为合适)的一个数据结构,即可以从channel里面塞数据,也可以从中获取数据。channel本身并没有什么神奇的地方,但是channel加上了goroutine,就形成了一种既简单又强大的请求处理模型,即N个工作goroutine将处理的中间结果或者最终结果放入一个channel,另外有M个工作goroutine从这个channel拿数据,再进行进一步加工,通过组合这种过程,从而胜任各种复杂的业务模型。

当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。

f()    // 调用f()函数,并等待f()返回
go f() // 创建一个新的goroutine去执行f(), 不需要等待

下面的例子,main goroutine将计算菲波那契数列的第45个元素值。由于计算函数使用低效的递归,所以会运行相当长时间,在此期间我们想让用户看到一个可见的标识来表明程序依然在正常运行,所以来做一个动画的小图标:

func main() {
    go spinner(100 * time.Millisecond)
    const n = 45
    fibN := fib(n) // slow
    fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
    for {
        for _, r := range `-\|/` {
            fmt.Printf("\r%c", r)
            time.Sleep(delay)
        }
    }
}

func fib(x int) int {
    if x < 2 {
        return x
    }
    return fib(x-1) + fib(x-2)
}

动画显示了几秒之后,fib(45)的调用成功地返回,并且打印结果:

Fibonacci(45) = 1134903170

然后主函数返回。主函数返回时,所有的goroutine都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让被请求的goroutine自行结束执行

留意一下这里的两个独立的单元是如何进行组合的,spinning和菲波那契的计算。分别在独立的函数中,但两个函数会同时执行。

results matching ""

    No results matching ""