go的并发模型
介绍并发模型,我们先来说一下并发和并行。
并发和并行
并发和并行否是为了充分利用CPU多核计算资源提出来的概念。
- 并发指的是在同一个时间段内,多条指令在CPU上同时执行
- 并行值得是在同一时刻,多条指令在CPU上同时执行
并发程序其实并不要求CPU具备多核计算的能力,在同一时间段内,多个线程会被分配一定的执行时间片,在CPU上被快速轮换执行。
CSP并发模型
Go语言中实现了两种并发模型,一种是依赖于共享内存实现的线程-锁并发模型,另一种则是CSP。
CSP倡导使用通信来共享内存,它有两个关键点:
- 并发实体,通常可以理解为执行线程,它们相互独立且可以并发执行
- 通道,并发实体之间通过通道发送消息,进行通信
CSP 类似于我们常用的同步队列,它关注的消息传输的方式(通道),并不关注消息实体。发送者和接收者可能并不知道对方是谁,耦合度是很低的。
虽然CSP的通道提供了极大的灵活性,但作为独立的对象,它可以被任意并发实体创建、读取、写入、使用,但使用时务必注意,当一个并发实体在读取一个永远没有数据放入的通道或者把数据放入一个永远不会被读取的通道,是会被阻塞,发生死锁的。
MPG线程模型
Go语言不但有着独特的并发编程模型,还拥有强大的用于调度goroutine、对接系统级线程的调度器。
这个调度器是Go语言运行时系统的重要组成部分,它主要负责统筹调配Go并发编程模型中的三个主要元素,即:MPG。
模块 | 说明 |
---|---|
Machine | 一个Machine对应一个内核线程,相当于内核线程在Go进程中的映射 |
Processor | 一个Processor表示执行Go程序所必须的上下文环境,可以理解为用户代码逻辑的处理器 |
Goroutine | 是对Go语言中代码片段的封装,其实是一个轻量级的用户线程 |
M和P是一对一绑定的,但由于P的存在,G和M可以呈现出多对多的关系。当一个正在与某个M对接并运行着的G,需要因某个事件(比如等待I/O或锁的解除)而暂停运行的时候,调度器总会及时地发现,并把这个G与那个M分离开,以释放计算资源供那些等待运行的G使用。
可以看图,更直观:
有关P和M的个数问题
- P的数量:
由启动时环境变量$GOMAXPROCS或者是由runtime的方法GOMAXPROCS()决定。这意味着在程序执行的任意时刻都只有$GOMAXPROCS个goroutine在同时运行。
- M的数量:
go语言本身的限制:go程序启动时,会设置M的最大数量,默认10000。但是内核很难支持这么多的线程数,所以这个限制可以忽略。
runtime/debug中的SetMaxThreads函数,设置M的最大数量。
M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。
P和M何时会被创建:
- P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P。
- M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M。
参考:
- 《Go语言高并发与微服务实战》
- 《Go语言核心36讲》
- Golang调度器GMP原理与调度全分析