在上一篇教程中,我们讨论了并发性以及它与并行性的区别。在本教程中,我们将讨论如何在Go中使用Goroutines实现并发。
什么是Goroutines?
Goroutines是与其他函数或方法同时运行的函数或方法。Goroutines可以被认为是轻量级的线程。与线程相比,创建一个Goroutine的成本很小。因此,在Go应用程序中,通常会有成千上万的Goroutine同时运行。
相对于线程,Goroutines的优势
- 与线程相比,Goroutines非常便宜。它们的堆栈大小只有几kb,而且堆栈可以根据应用程序的需要进行增减,而在线程的情况下,堆栈大小必须被指定并且是固定的。
- Goroutines被复用到数量较少的操作系统线程中。在一个有数千个Goroutine的程序中,可能只有一个线程。如果该线程中的任何一个Goroutine阻塞了,例如等待用户输入,那么就会创建另一个OS线程,其余的Goroutine被转移到新的OS线程。所有这些都是由运行时处理的,而我们作为程序员被从这些复杂的细节中抽象出来,并被赋予一个干净的API来处理并发问题。
- 线程使用通道进行通信。通道的设计可以防止在使用Goroutines访问共享内存时发生竞赛条件。通道可以被认为是一个channels,Goroutines使用它进行通信。我们将在下一个教程中详细讨论channels。
如何启动一个Goroutine?
在函数或方法的调用前加上关键字go
,你就会有一个新的Goroutine同时运行。
让我们来创建一个Goroutine :)
1 | package main |
在第11行,go hello()
启动了一个Goroutine。现在hello()
函数将与main()
函数一起并发运行。主函数在它自己的Goroutine中运行,它被称为主Goroutine。
运行这个程序,你会有一个惊喜!
这个程序只输出main函数的文本。我们启动的Goroutine发生了什么?我们需要了解Goroutine的两个主要属性来理解为什么会发生这种情况。
- 当一个新的Goroutine被启动时,Goroutine的调用立即返回。与函数不同,控件不会等待Goroutine执行完毕。控件会立即返回到Goroutine调用后的下一行代码,Goroutine的任何返回值都会被忽略。
- 主Goroutine应该在运行中,其他Goroutine才能运行。如果主Goroutine终止了,那么程序就会被终止,其他Goroutine就不会运行。
我想现在你应该能够理解为什么我们的Goroutine没有运行了。在调用了第11行的hello()
之后,控制权立即返回到主函数的下一行代码,没有等待hello goroutine完成并打印main function
。然后main Goroutine终止了,因为没有其他代码可以执行,因此hello
Goroutine没有机会运行。
现在我们来解决这个问题。
1 | package main |
在上述程序的第13行,我们调用了时间包的Sleep方法,使正在执行的go程序处于睡眠状态。在这种情况下,main goroutine被睡眠1秒钟。现在对go hello()的调用在主Goroutine终止之前有足够的时间来执行。这个程序首先打印出Hello world goroutine,等待1秒钟,然后打印出main函数。
这种在主Goroutine中使用sleep来等待其他Goroutine完成执行的方式是我们用来理解Goroutine如何工作的一个黑客。通道可以用来阻止主Goroutine,直到所有其他Goroutine完成它们的执行。我们将在下一个教程中讨论通道。
启动多个Goroutine
让我们再写一个启动多个Goroutine的程序来更好地理解Goroutine。
1 | package main |
上述程序在第21行和第22行启动了两个Goroutine。这两个Goroutine现在同时运行。numbers
Goroutine最初睡眠250毫秒,然后打印1
,然后再次睡眠并打印2
,同样的循环发生,直到它打印5
。同样地,alphabets
Goroutine打印从a
到e
的字母,每次执行有400毫秒的睡眠时间。main
Goroutine启动了numbers
和alphabets
Goroutine,睡眠时间为3000毫秒,然后终止。
这个程序的输出:
1 | 1 a 2 3 b 4 c 5 d e main terminated |
下面的图片描述了这个程序如何工作。请在新的标签页中打开图片,以获得更好的视觉效果 :)
图片的第一部分用蓝色表示numbers
Goroutine,第二部分用栗色表示alphabets
Goroutine,第三部分用绿色表示main
Goroutine,最后一部分用黑色表示合并上述三部分,向我们展示了程序的工作原理。每个方框顶部的0毫秒、250毫秒等字符串代表了以毫秒为单位的时间,每个方框底部的输出表示为1、2、3,等等。蓝色方框告诉我们,250 ms
后打印1
,500 ms
后打印2
,以此类推。最后一个黑框的底部有数值1 a 2 3 b 4 c 5 d e main terminated
,这也是程序的输出。这张图是不言自明的,你将能够理解这个程序的工作原理。
这就是Goroutines的内容了。祝你有一个愉快的一天。