Goroutine
Go语言中的goroutine概念类似于线程,但goroutine是由Go的运行时调度和管理的。Go程序会智能地将goroutine中的任务合理分配给每个CPU。Go语言之所以被称为现代化地编程语言,是因为语言层面内置了调度和上下文切换机制。
在Go语言中不需要自己去写线程,进程 ,协程,技能包里面只有一个goroutine当 需要某个任务并发执行的时候,只需要把这个任务包装成一个函数,再开启一个goroutine去执行就可以了。
使用goroutine
Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上关键字,就可以为一个函数创建一个goroutine
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数
启动单个goroutine
```go 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)//停顿一毫秒的方式等待一下 然后在运行查看 }
<a name="5996816a"></a>#### 启动多个goroutine> 在Go语言中实现并发只需要go func()就可以实现> ```gofunc 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 0main main mainhello hello hello 5hello hello hello 1hello hello hello 4hello hello hello 6hello hello hello 7hello hello hello 8hello hello hello 2hello hello hello 3hello 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,但不会太多,切换频繁的话得不偿失。
