说到并发,初学者对于这个概念经常和并行混淆。其实我们可以从字面意思很容易的理解他们。并发,就是同时发生,而并行,就是同时进行。

以咖啡店为例,两人排两队同时点咖啡,而接到订单一个咖啡师做两杯咖啡,这是并发。两个咖啡师同时工作,每人做一杯咖啡,这是并行。同理回到程序中,多线程程序在单核上运行,就是并发;多线程程序在多核上运行,就是并行。

Go语言的并发不同于Python基于多线程编程模型,而是采用了基于消息并发模型的方式。它将基于CSP模型的并发编程内置到了语言中,通过一个go关键字就可以轻易地启动一个Goroutine,而且在Goroutine之间是共享内存的。

在Go语言中通过自己的调度器实现了CSP模型,这个调度器也就是MPG调度模型:

MPG

M 代表内核线程;

P 代表程序执行上下文,将等待执行的G与M对接。Go的运行时系统会适时地让P与不同的M建立或断开关联,以使P中的那些可运行的G能够及时获得运行时机;

G代表协程,可以有多个;

图中两个M如果运行在一个CPU上就是并发,如果运行在不同CPU就是并行。

知道了MPG模型,那Go语言的MPG又是怎样实现调度的呢?下面通过图解来看一下。

对于一个运行的GO程序,在调度器内部存在着:
1. 全局M,P,G列表,分别用于记录当前所有的M,P,G信息;

2. 空闲M,P,G列表,分别用于记录当前空闲可调度的M,P,G信息,以减少M,P,G的创建从而提升性能;

3. 可运行G列表,存放等待调度器分配给P的G。

MPG调度器只关注单独的Go程序中的Goroutine,如上图的G0。Go Goroutine采用的是半抢占式的协作调度,只有在G0发生阻塞时才会导致调度,否则会依次执行P绑定的其他G;

当G0阻塞时,调度器会将P与当前的M0和G0解绑,同时去空闲M列表中找新的M,如果没有则创建M1,绑定P,顺序执行P下的G。

当G0阻塞结束后,调度器会到空闲P列表中为M0找空闲可绑定的P,如果恰巧有P,则继续执行G0。如果没有可用的P,则如下图所示:(注:这个过程中,M1和M0可能并行)

M0被放入空闲列表,等待调度给需要的G;G0被放入可运行的G列表,列表中的G会经由调度再次放入某个P的可运行G队列。

至此一个简单的MPG流程就完成了。实际程序运行在多核下,同时会创建更多的协程,但是MPG的调度流程是一样的。

【来思Go】,Let's Go!欢迎关注留言交流学习!