Part21: Goroutines

欢迎来到Go教程系列的第 21 教程

文章目录

  • 什么是协程(Goroutines)?
  • Goroutines 优于线程的优势
  • 如何启动一个协程(Goroutine)?
  • 启动多个协程

在 上一教程我们讨论了关于并发以及它和并行的不同,在该教程,我们将讨论关于并发在Go中是如何使用Goroutines实现的。
什么是协程(Goroutines)? 协程(Goroutines)是与其他函数或方法同时运行的函数或方法。Go协程可以被想到轻量级线程。与线程相比,创建一个协程的花费是小的。因此通常 Go 应用程序并发地运行数千个协程。
Goroutines 优于线程的优势
  • 和线程相比,协程极其便宜。他们在堆栈大小只有几 kb 字节,而且堆栈可以根据应用程序的需要增长和收缩,而线程的堆栈大小必须指定并固定。
  • 协程被复用到需要少许的系统线程上。程序中的可能仅有一个拥有数千协程的线程。如果这个线程的任何协程被阻塞以等待用户的输入,其他系统线程会被创建并且其余的协程会被移动到新的系统线程上。所有这些都由运行时处理,我们作为程序员从这些复杂的细节中抽象出来,并给出一个干净的API来处理并发。
  • 协程使用通道来通信。当使用协程访问共识内存时,通道的设计阻止竞态的出现。通道可以被想象为使用协程通信的管道。我们将在下一教程讨论通道的细节。
如何启动一个协程(Goroutine)? 在函数或方法的调用前使用关键字 go,你将得到一个并发运行的协程。
我们来创建一个协程
package mainimport ( "fmt" )func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() fmt.Println("main function") }

在 11 行,go hello() 启动一个新的协程。现在 hello() 函数与 main.() 函数并发运行。main 函数运行在它自己的协程里,它被称为 main 协程。
运行这个程序你会有个惊喜
这个程序只输出文本 main function。我们启动的协程发生了什么 ?我们需要理解两个 go 协程的两个主要属性来理解为什么会发生这种情况。
  • 当新的协程(Goroutine)启动后,协程调用会立即返回,不像函数,控制不会等等协程执行完成。控制会立即返回,执行协程调用后面的下一行代码,任何协程的返回值会被忽略。
  • 主协程应该为其他任何协程的运行而运行。如果主协程终止了,那么程序将终止,没有其他协程运行。
我猜现在你可以理解为什么我们的协程没有运行了。在 11 行调用 go hello() 之后,控制立即返回下一行代码打印 main function,并没有等待 hello 协程完成。然后 main 协程终止了,因为没有其他代码要执行,所以 hello 协程没有任何运行的机会。
现在我们来解决它。
package mainimport ( "fmt" "time" )func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() time.Sleep(1 * time.Second) fmt.Println("main function") }

在上面程序的 13 行,我们调用了 time 包的 Sleep 方法,它休眠它所运行的 go 协程,这种情况下,主协程进入休眠状态 1 秒。现在调用 go hello() 在主协程终止之前有足够的时间运行。这个程序首先打印 Hello world goroutine ,等待 1 秒后打印 main function
*这种在主协程中使用 sleep 来等待其他协程完成他们的运行是我们用来理解 Goroutines 如何工作的契子。可以使用通道来阻塞主协程的运行等待所有其他协程完成它们的运行。我们将在下一教程的通道中讨论这些。
启动多个协程 我们再写一个启动多个协程的程序来更好的理解Goroutines。
package mainimport ( "fmt" "time" )func numbers() { for i := 1; i <= 5; i++ { time.Sleep(250 * time.Millisecond) fmt.Printf("%d ", i) } } func alphabets() { for i := 'a'; i <= 'e'; i++ { time.Sleep(400 * time.Millisecond) fmt.Printf("%c ", i) } } func main() { go numbers() go alphabets() time.Sleep(3000 * time.Millisecond) fmt.Println("main terminated") }

上面的程序在 21 行和22 行启动两个协程。这两个协程现在并发地运行。numbers 协程先休眠 250 毫秒,然后打印 1,然后再次休眠,打印 2,然后是同样的循环,直到它的打印 5。类似地,alphabets 协程从 ae 打印字母,有400毫秒的休眠时间。主协程启动 numbersalphabets 后,休眠 3000 毫秒然后终止。
这个程序输出
1 a 2 3 b 4 c 5 d e main terminated

下面描绘了这个程序的工作原理,请在另一个新的标签打开图像以直观。
Part21: Goroutines
文章图片

用蓝色标示的图片第一部分代表numbers Goroutine,栗色标示的第二部分代表alphabets Goroutine,绿色标志的第三部分代表main Goroutine。用黑色标示的最后一部分合并了所有上面三部分向我们展示了程序如何工作。在每个盒子顶部标示的像 0ms,250ms 这样的字符串代表以毫秒为单位的时间,盒子底部如1,2,3等代表输出。
【Part21: Goroutines】下一教程 - 通道

    推荐阅读