Goroutine是Go中最基本的组织单位之一,所以了解它是什么以及它如何工作是非常重要的。事实上,每个Go程序至少拥有一个:main gotoutine,当程序开始时会自动创建并启动。在几乎所有Go程序中,你都可能会发现自己迟早加入到一个gotoutine中,以帮助自己解决问题。那么它到底是什么?
简单来说,gotoutine是一个并发的函数(记住:不一定是并行)和其他代码一起运行。你可以简单的通过将go关键字放在函数前面来启动它:
func main() { go sayHello() // continue doing other things }
func sayHello() { fmt.Println("hello") } 对于匿名函数,同样也能这么干,从下面这个例子你可以看得很明白。在下面的例子中,我们不是从一个函数建立一个goroutine,而是从一个匿名函数创建一个goroutine:
go func() { fmt.Println("hello") }()// 1 // continue doing other things 注意这里的(),我们必须立刻调用匿名函数来使go关键字有效。 或者,你可以将函数分配给一个变量,并像这样调用它:
sayHello := func() { fmt.Println("hello") } go sayHello() // continue doing other things 看起来很简单,对吧。我们可以用一个函数和一个关键字创建一个并发逻辑块,这就是启动goroutine所需要知道的全部。当然,关于如何正确使用它,对它进行同步以及如何组织它还有很多需要说明的内容。本章接下来的部分会深入介绍goroutine及它是如何工作的。如果你只想编写一些可以在goroutine中正确运行的代码,那么可以考虑直接跳到下一章。
那么让我们来看看发生在幕后的事情:goroutine实际上是如何工作的? 是OS线程吗? 绿色线程? 我们可以创建多少个?
Goroutines对Go来说是独一无二的(尽管其他一些语言有类似的并发原语)。它们不是操作系统线程,它们不完全是绿色的线程(由语言运行时管理的线程),它们是更高级别的抽象,被称为协程(coroutines)。协程是非抢占的并发子程序,也就是说,它们不能被中断。
Go的独特之处在于goutine与Go的运行时深度整合。Goroutine没有定义自己的暂停或再入点; Go的运行时观察着goroutine的行为,并在阻塞时自动挂起它们,然后在它们变畅通时恢复它们。在某种程度上,这使得它们可以抢占,但只是在goroutine被阻止的地方。它是运行时和goroutine逻辑之间的一种优雅合作关系。 因此,goroutine可以被认为是一种特殊的协程。
协程,因此可以被认为是goroutine的隐式并发构造,但并发并非协程自带的属性:某些东西必须能够同时托管几个协程,并给每个协程执行的机会,否则它们无法实现并发。当然,有可能有几个协程按顺序执行,但看起来就像并行一样,在Go中这样的情况比较常见。
Go的宿主机制实现了所谓的M:N调度器,这意味着它将M个绿色线程映射到N个系统线程。 Goroutines随后被安排在绿色线程上。 当我们拥有比绿色线程更多的goroutine时,调度程序处理可用线程间goroutines的分布,并确保当这些goroutine被阻塞时,可以运行其他goroutines。我们将在第六章讨论所有这些机制是如何工作的,但在这里我们将介绍Go如何对并发进行建模。
Go遵循称为fork-join模型的并发模型.fork这个词指的是在程序中的任何一点,它都可以将一个子执行的分支分离出来,以便与其父代同时运行。join这个词指的是这样一个事实,即在将来的某个时候,这些并发的执行分支将重新组合在一起。子分支重新加入的地方称为连接点。这里有一个图形表示来帮助你理解它: