并发的介绍 golang是天然支持高并发的语言 并发:同一时间内执行多个任务 并行:同一时刻执行多个任务 golang的并发通过goroutine实现,goroutine是用户态的线程,由go的运行时runtime调度,而线程是通过操作系统去调度。 最终还是放在操作系统的线程去执行,只不过管理(上下文切换)放在用户态,所以开销较小。 golang提供channel用来在多个goroutine之间进行通信。 只需要在调用函数前加上 go 关键字 即可开启一个goroutine goroutine什么时候结束? goroutine对应的函数执行结束了,goroutine就结束了 main函数执行完毕,由main函数创建的那些goroutine就结束了 如何等待所有的goroutine结束之后再执行主函数? sync.waitGroup对象中有三个方法 var wg sync.waitGroup wg.add(1) //计数器加1 wg.done()//计数器减1 wg.wait()//等待所有的goroutine结束 goroutine与线程 os线程有固定的栈内存。一般是2MB,一个goroutine的生命周期开始的时候占用的内存是2kb goroutine的栈内存不是固定的,可以按需扩大或者缩小,最大1Gb goroutine的调度 G: 即goroutine,里面除了存放本goroutine的信息之外还有与所在的p的绑定等信息。 M: machine是go运行时对操作系统线程内核线程的虚拟,M与内核线程一般是一一映射的关系,一个goroutine最终是要放到M上 去运行的。 P: P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址,以及地址边界),P会对自己管理的goroutine队列做一些调度 当自己的队列消费完成之后就去全局队列里面取,如果全局队列里也消费完了回去其他P的队列里抢任务。 P和M一般也是一一对应的,他们的关系是:P管理着一组G挂载在M上运行,当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G挂载在新的M上, 当旧的G阻塞完成或者认为其已经死掉时,回收旧的M。 P的个数通过runtime.GOMAXPROC指定,最大256,GO1.5版本后默认为物理线程数,在并发量大的时候最增加一些P和M,但不会太多 M:N 复用/调度M的goroutine的到N个os线程 一个操作系统的线程对应多个用户态的goroutine 狗程序可以同时使用多个操作系统线程 goroutine和os线程时多对多的关系,即M:N select多路复用 Select 的使用方式类似于之前学到的 switch 语句,它也有一系列 case 分支和一个默认的分支。 每个 case 分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case 分支对应的语句。具体格式如下: select { case <-ch1: //... case data := <-ch2: //... case ch3 <- 10: //... default: //默认操作 }