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)//停顿一毫秒的方式等待一下 然后在运行查看 }

  1. <a name="5996816a"></a>
  2. #### 启动多个goroutine
  3. > 在Go语言中实现并发只需要go func()就可以实现
  4. > ```go
  5. func hello(i int) {
  6. fmt.Println("hello hello hello", i)
  7. }
  8. func main() {
  9. for i := 0; i < 10; i++ {
  10. go hello(i)
  11. }
  12. fmt.Println("main main main")
  13. time.Sleep(time.Second) //停顿一毫秒
  14. }
  15. hello hello hello 0
  16. main main main
  17. hello hello hello 5
  18. hello hello hello 1
  19. hello hello hello 4
  20. hello hello hello 6
  21. hello hello hello 7
  22. hello hello hello 8
  23. hello hello hello 2
  24. hello hello hello 3
  25. 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,但不会太多,切换频繁的话得不偿失。