Goroutine协程
Goroutine
Go语言中的goroutine概念类似于线程,但goroutine是由Go的运行时调度和管理的。Go程序会智能地将goroutine中的任务合理分配给每个CPU。Go语言之所以被称为现代化地编程语言,是因为语言层面内置了调度和上下文切换机制。
在Go语言中不需要自己去写线程,进程 ,协程,技能包里面只有一个goroutine当 需要某个任务并发执行的时候,只需要把这个任务包装成一个函数,再开启一个goroutine去执行就可以了。
使用goroutine
Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上关键字,就可以为一个函数创建一个goroutine
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数
启动单个goroutine
package main import "fmt" func hello(){ fmt.Println("hello hello hello") } func main() { go hello() //运行多次可能会发现 有时候只打印了main方法一行。原因是在程序启动时候。Go程序就会为main函数默认创建一个goroutine。当main函数返回的时候该goroutine就结束了。所有在main函数中启动的goroutine函数会一天结束。所以有时候goroutine还没执行到hello 函数就已经没了 fmt.Println("main main main") //可以采用time.Sleep(time.Second)//停顿一毫秒的方式等待一下 然后在运行查看 }
启动多个goroutine
在Go语言中实现并发只需要go func()就可以实现
func hello(i int) { fmt.Println("hello hello hello", i) } func main() { for i := 0; i < 10; i++ { go hello(i) } fmt.Println("main main main") time.Sleep(time.Second) //停顿一毫秒 } hello hello hello 0 main main main hello hello hello 5 hello hello hello 1 hello hello hello 4 hello hello hello 6 hello hello hello 7 hello hello hello 8 hello hello hello 2 hello hello hello 3 hello hello hello 9
通过上面的结果可以发现打印的结果顺序不一致,这是因为10个goroutine是并发执行的而goroutine的调度是随机的
注意
如果主协程退出了,其他任务不再执行。当主协程返回的时候,goroutine就都结束了。
goroutine的栈大小
OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB)一个goroutine的栈其在生命周期开始时只有很小的栈(典型情况瞎时2KB),goroutine的栈不是固定的,它可以按需增大和缩小,goroutine的栈大小限制可以达到1GB.虽然极少会用到这个大。所以在Go语言中一次性创建10w左右的goroutine时可行的。
goroutine 调度
GPM时GO语言运行时的实现,是Go实现的一套调度系统。区别于操作系统调度OS线程
- G就是goroutine。里面除了存放本goroutine信息外还有与所在P的绑定等信息
- P管理者一组goroutine队列,P里面会存储当前goroutine运行的上下文环境,P会对自己管理的goroutine队列做一些调度当自己队列消费完了就去全局队列里面取。如果全局队列里也消费完了就去其他P里面去抢任务。
- M是Go运行时对操作系统内核线程的的虚拟,M与内核线程一般是--映射的管理,一个groutine最终是要放到M上执行的。
P与M一般是一一对应的。P管理一组G挂在在M上运行。
当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G挂在在新建的M上。
当旧的G阻塞完成或者认为其已经死掉时回收旧的M。P的个数是通过runtime.GOMAXPROCS设定(最大256).GO1.5版本之后默认为物理线程数。在并发量大的时候会增加一些P和M,但不会太多,切换频繁的话得不偿失。