GO语言学习笔记-Goroutines
原文,建议理解并发(concurrency)、并行(parallelism)区别后再看这方面的内容。
Goroutines是啥?
Goroutines是一个可以和其他函数或方法并发执行的函数或方法。也可以把它理解为轻量级的线程(roy注:这话听起来和大python中的协程很像啊!),而创建Goroutine的开销却远远小于线程。因此在大多数的Go程序都可以并发执行成千上万的Goroutine。
Goroutines的优势
- Goroutine和线程相比及其节省开销,它们仅需要占用几kb的栈空间,而且栈空间可以根据程序的需要增加或回收。而同样情形下线程占用的栈空间只能被指定并且不可修改。
- Goroutine采用多路复用的方式来减少对系统线程的占用。程序中一个线程中可能包含几千个Goroutine,如果任何Goroutine在线程中阻塞比如需要等待用户输入,那么将会创建新的系统线程并把剩下的Goroutine转移到新系统线程中。所有的操作都由运行时自动处理,作为开发人员不用纠结于实现这个的细节了,Go为此提供了清晰的API。
- Goroutine之间的通信使用channel,channel被设计用来防止Goroutine之间访问共享内存可能造成的冲突。channel可以理解为Goroutine通信的管道。我们将在后面的文章中讨论channel。
如何启动一个Goroutine
在调用函数或者方法时前面加上go
你将启动一个新的Goroutine。
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}
在11行,go hello()
启动了一个新的Goroutine,现在hello()
函数将和main()
函数并发执行,main()
函数在其拥有的被称为main Goroutine
的Goroutine中执行。
运行上面代码,你将发现只有main function
被输出了,搞毛线?我们需要理解Goroutine的2个主要的属性来解释为什么会这样。
- 当一个新Goroutine开始执行时将会立刻返回。和函数不同,程序并不会等待Goroutine执行结束,而是立刻执行下一行代码并且忽略Goroutine的返回值。
- 如果
main Goroutine
结束,那么程序就结束了而且所有的Goroutine都不会运行。
我猜你已经明白为什么我们的Goroutine没有执行了。11行程序立刻执行了下一行输出了main function
而不是等待Goroutine执行结束。main Goroutine
执行结束后其他代码没有机会执行,所以hello
Goroutine没机会执行。
现在我们修复这个问题:
package main
import (
"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
方法,这样main Goroutine
被阻塞1秒钟,程序结束前go hello()
有足够的时间执行。所以这个程序先输出Hello world goroutine
,然后输出main function
。
在main Goroutine
中使用Sleep
来等待其他Goroutine执行完毕仅仅是用来方便理解Goroutine如何工作,Channel
才是用来阻塞main Goroutine
等待其他Goroutine执行完毕的主要方式。我们将在下一篇文章解释。
启动多个Goroutine
让我们写个启动多个Goroutine的程序来更好的理解:
package main
import (
"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")
}
上面的程序启动了2个Goroutine,这2个Goroutine并发执行。numbers
sleep 250毫秒后输出1
然后再次sleep输出2
,直到循环结束输出5
。同样的alphabets
函数输出a
到e
,每次sleep 400毫秒。main Goroutine
启动上面2个Goroutine后sleep 3000毫秒并结束。
程序输出如下:
1 a 2 3 b 4 c 5 d e main terminated
原文最后还有个图解释输出为啥是上面那样,这里roy就不翻译了,不明白的可以自己去原文中查看。