【go语言学习】并发实现
一、什么是goroutine
Goroutine是Go语言特有的名词。区别于进程Process,线程Thread,协程Coroutine,因为Go语言的创造者们觉得和他们是有所区别的,所以专门创造了Goroutine。
Goroutine是与其他函数或方法同时运行的函数或方法。Goroutines可以被认为是轻量级的线程。与线程相比,创建Goroutine的成本很小,它就是一段代码,一个函数入口。以及在堆上为其分配的一个堆栈(初始大小为4K,会随着程序的执行自动增长删除)。因此它非常廉价,Go应用程序可以并发运行数千个Goroutines。
二、主goroutine
封装main函数的goroutine称为主goroutine。
主goroutine所做的事情并不是执行main函数那么简单。它首先要做的是:设定每一个goroutine所能申请的栈空间的最大尺寸。在32位的计算机系统中此最大尺寸为250MB,而在64位的计算机系统中此尺寸为1GB。如果有某个goroutine的栈空间尺寸大于这个限制,那么运行时系统就会引发一个栈溢出(stack overflow)的运行时恐慌。随后,这个go程序的运行也会终止。
此后,主goroutine会进行一系列的初始化工作,涉及的工作内容大致如下:
- 创建一个特殊的defer语句,用于在主goroutine退出时做必要的善后处理。因为主goroutine也可能非正常的结束
- 启动专用于在后台清扫内存垃圾的goroutine,并设置GC可用的标识
- 执行mian包中的init函数
- 执行main函数
- 执行完main函数后,它还会检查主goroutine是否引发了运行时恐慌,并进行必要的处理。最后主goroutine会结束自己以及当前进程的运行。
三、如何使用goroutine
Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。
package main
import (
"fmt"
)
func main() {
go f1()
fmt.Println("你好,世界")
}
func f1() {
fmt.Println("hello world")
}
运行结果
你好,世界
或者
hello world
你好,世界
多次运行以上程序,会出现不同的输出结果。
这是因为:上面的程序有main函数主goroutine,和f1函数子goroutine。程序运行时,当子goroutine优先抢到系统资源,就会输出hello world
,然后主goroutine输出你好,世界
,结束程序;当主goroutine优先抢到系统资源,就会输出你好,世界
,然后主goroutine就结束了,所有在main()函数中启动的goroutine会一同结束。
修改以上代码:
package main
import (
"fmt"
"time"
)
func main() {
go f1()
fmt.Println("你好,世界")
time.Sleep(1 * time.Second)
}
func f1() {
fmt.Println("hello world")
}
运行结果
你好,世界
hello world
增加了主goroutine睡眠1s,这样子goroutine有足够的时间来执行。
四、启动多个goroutine
package main
import (
"fmt"
"time"
)
func main() {
go numbers()
go alphabets()
time.Sleep(3000 * time.Millisecond)
fmt.Println(" main over")
}
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)
}
}
运行结果
1a23b4c5de main over