什么是 Goroutines?

Goroutines 是与其他函数(方法)同时运行的函数(方法)。 Goroutines 可以被认为是轻量级线程。与线程相比,创建 Goroutine 的成本很小。因此,Go 应用程序通常会同时运行数千个 Goroutines。

Goroutines 对比线程的优势

  • 与线程相比,goroutine 开销非常小。它们的堆栈大小只有几个 kb,并且堆栈可以根据应用程序的需要扩大或者缩小,而在线程的情况下,堆栈大小必须指定并且是固定的。

  • Goroutines 被多路复用到较少数量的 OS 线程。程序中可能只有一个线程,但是可以有上千个 Goroutines。如果线程块中的任何 Goroutine 等待用户输入,则创建另一个 OS 线程并将剩余的 Goroutines 移到新的 OS 线程。所有这些都是在运行时处理,我们作为程序员从这些复杂的细节中抽象出来,并给出一个干净的 API 来处理并发。

  • Goroutines 使用 channels 进行通信。channels 可防止使用 Goroutines 访问共享内存时发生竞争条件。channels 可以被认为是 Goroutines 通信的管道。我们将在下一个教程中详细讨论 channels。


如何开启一个Goroutine?


在函数或方法调用前面加上关键字 go,就可以同时运行一个新的 Goroutine。

让我们创建一个 Goroutine:)

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func hello() {
  6. fmt.Println("Hello world goroutine")
  7. }
  8. func main() {
  9. go hello()
  10. fmt.Println("main function")
  11. }

Run program in playground

在第 11 行中,go hello() 启动了一个新的 Goroutine。现在 hello() 函数将与main() 函数同时运行。main函数在它自己的 Goroutine 中运行,它被称为 main Goroutine

运行这个程序,你会有一个惊喜。
_
该程序仅输出 main function。我们启动的 Goroutine 发生什么了?我们需要了解 Goroutine 的两个主要属性来了解为什么会发生这种情况。

  • 当一个新的 Goroutine 启动时,Goroutine 调用立即返回。与函数不同,主程序不等待 Goroutine 完成执行。主程序在 Goroutine 调用之后立即返回到下一行代码,并且忽略 Goroutine 中的任何返回值。

  • 任何的 Goroutines 都跑在**主程序的 Goroutine 上**运行。如果主程序 Goroutine 终止,那么程序将被终止,没有其他 Goroutine 将运行。


我想现在你将能够理解为什么我们的 Goroutine 没有跑起来。第 11 行调用完 go hello() 后。 主程序立即返回到下一行代码,不会等待 goroutine hello 完成,所以输出 main function。然后主程序 Goroutine 结束,因为没有其他代码可以执行,因此 hello Goroutine 没有机会跑完。

现在让我们来解决这个问题。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func hello() {
  7. fmt.Println("Hello world goroutine")
  8. }
  9. func main() {
  10. go hello()
  11. time.Sleep(1 * time.Second)
  12. fmt.Println("main function")
  13. }

Run program in playground

在上面的程序第 14 行,我们调用了 time 包的 Sleep 方法,它能程序休眠。在这个程序中,将主的 goroutine 休眠 1 秒。现在,go hello() 有足够的时间在主 Goroutine 终止之前执行。这个程序首先打印 Hello world goroutin,等待 1 秒,然后打印 main function

这种在主程序 Goroutine 中使用 sleep 来等待其他 Goroutine 完成执行的方法是一种 hack 手段,我们使用它主要是来了解 Goroutine 是如何工作的。channels 也可以用来阻塞主 Goroutine,直到所有其他的 Goroutine 执行完毕。我们将在下一篇教程中讨论 channels
_

启动多个Goroutines


让我们再写一个程序,启动多个 Goroutines 来更好地理解 Goroutines。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func numbers() {
  7. for i := 1; i <= 5; i++ {
  8. time.Sleep(250 * time.Millisecond)
  9. fmt.Printf("%d ", i)
  10. }
  11. }
  12. func alphabets() {
  13. for i := 'a'; i <= 'e'; i++ {
  14. time.Sleep(400 * time.Millisecond)
  15. fmt.Printf("%c ", i)
  16. }
  17. }
  18. func main() {
  19. go numbers()
  20. go alphabets()
  21. time.Sleep(3000 * time.Millisecond)
  22. fmt.Println("main terminated")
  23. }

Run in playground

上面的程序 23、24 行中启动了两个 Goroutines。这两个 Goroutines 现在同时运行。Goroutine numbers 最初睡眠 250 毫秒然后输出 1,然后再次睡眠并输出 2。并且相同的循环发生直到它输出 5。类似地,Goroutine alphabets 从 a 到 e 输出字母并且每次是 400 毫秒的时间间隔。主程序 Goroutine 启动 numbersalphabets Goroutines,睡眠 3000 毫秒,然后终止程序。

程序输出

  1. 1 a 2 3 b 4 c 5 d e main terminated

下图描绘了该程序的工作原理。请在新标签页中打开图片以获得更好的图片体验:)

第二十一部分:Goroutines - 图1

蓝色图像的第一部分代表 numbers Goroutine,栗色的第二部分代表 alphabets Goroutine,绿色的第三部分代表主程序 Goroutine,黑色的最后部分合并以上三个来告诉我们程序是如何工作的。每个框顶部的 0 ms,250 ms 等字符串表示以毫秒为单位的时间,输出在每个框的底部表示为 1,2,3 等等。蓝色框告诉我们在 250 ms 后打印 1,在 500 ms 后打印2,依此类推。最后一个黑框的底部的值为 1 a 2 3 b 4 c 5 d e main terminated,这也是该程序的输出。图像是我自己画的,希望你能够理解该程序的工作原理。

原文链接


https://golangbot.com/goroutines/