Go的CSP并发模型实现:M, P, G

        最近抽空研究、整理了一下Golang调度机制,学习了其余大牛的文章。把本身的理解写下来。若有错误,请指正!!!java

        golang的goroutine机制有点像线程池:
        1、go 内部有三个对象: P对象(processor) 表明上下文(或者能够认为是cpu),M(work thread)表明工做线程,G对象(goroutine).
        2、正常状况下一个cpu对象启一个工做线程对象,线程去检查并执行goroutine对象。碰到goroutine对象阻塞的时候,会启动一个新的工做线程,以充分利用cpu资源。全部有时候线程对象会比处理器对象多不少golang

        咱们用以下图分别表示P、M、G编程

       

在单核状况下,全部goroutine运行在同一个线程(M0)中,每个线程维护一个上下文(P),任什么时候刻,一个上下文中只有一个goroutine,其余goroutine在runqueue中等待。一个goroutine运行完本身的时间片后,让出上下文,本身回到runqueue中(以下图左边所示)。数组

当正在运行的G0阻塞的时候(能够须要IO),会再建立一个线程(M1),P转到新的线程中去运行。缓存

当M0返回时,它会尝试从其余线程中“偷”一个上下文过来,若是没有偷到,会把goroutine放到global runqueue中去,而后把本身放入线程缓存中。上下文会定时检查global runqueue。安全

 

Go语言是为并发而生的语言,Go语言是为数很少的在语言层面实现并发的语言;也正是Go语言的并发特性,吸引了全球无数的开发者。数据结构

并发(concurrency)和并行(parallellism)

并发(concurrency):两个或两个以上的任务在一段时间内被执行。咱们没必要care这些任务在某一个时间点是不是同时执行,可能同时执行,也可能不是,咱们只关心在一段时间内,哪怕是很短的时间(一秒或者两秒)是否执行解决了两个或两个以上任务。多线程

并行(parallellism):两个或两个以上的任务在同一时刻被同时执行。架构

并发说的是逻辑上的概念,而并行,强调的是物理运行状态。并发“包含”并行。并发

(详情请见:Rob Pike 的PPT

Go的CSP并发模型

Go实现了两种并发形式。第一种是你们广泛认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。

CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不一样于传统的多线程经过共享内存来通讯,CSP讲究的是“以通讯的方式来共享内存”。

请记住下面这句话:
Do not communicate by sharing memory; instead, share memory by communicating.
“不要以共享内存的方式来通讯,相反,要经过通讯来共享内存。”

普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通讯都是经过共享内存的方式来进行的。很是典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,经过锁来访问,所以,在不少时候,衍生出一种方便操做的数据结构,叫作“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。

Go的CSP并发模型,是经过goroutinechannel来实现的。

  • goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“相似,能够理解为”线程“。
  • channel是Go语言中各个并发结构体(goroutine)以前的通讯机制。 通俗的讲,就是各个goroutine之间通讯的”管道“,有点相似于Linux中的管道。

生成一个goroutine的方式很是的简单:Go一下,就生成了。

go f();

通讯机制channel也很方便,传数据用channel <- data,取数据用<-channel

在通讯过程当中,传数据channel <- data和取数据<-channel必然会成对出现,由于这边传,那边取,两个goroutine之间才会实现通讯。

并且无论传仍是取,必阻塞,直到另外的goroutine传或者取为止。

有两个goroutine,其中一个发起了向channel中发起了传值操做。(goroutine为矩形,channel为箭头)

左边的goroutine开始阻塞,等待有人接收。

这时候,右边的goroutine发起了接收操做。

右边的goroutine也开始阻塞,等待别人传送。

这时候,两边goroutine都发现了对方,因而两个goroutine开始一传,一收。

这即是Golang CSP并发模型最基本的形式。

Go并发模型的实现原理

咱们先从线程讲起,不管语言层面何种并发模型,到了操做系统层面,必定是以线程的形态存在的。而操做系统根据资源访问权限的不一样,体系架构可分为用户空间和内核空间;内核空间主要操做访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不能够直接访问资源,必须经过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。

咱们如今的计算机语言,能够狭义的认为是一种“软件”,它们中所谓的“线程”,每每是用户态的线程,和操做系统自己内核态的线程(简称KSE),仍是有区别的。

线程模型的实现,能够分为如下几种方式:

用户级线程模型

如图所示,多个用户态的线程对应着一个内核线程,程序线程的建立、终止、切换或者同步等线程工做必须自身来完成。

内核级线程模型

这种模型直接调用操做系统的内核线程,全部线程的建立、终止、切换、同步等操做,都由内核来完成。C++就是这种。

两级线程模型

这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现很是复杂,和内核级线程模型相似,一个进程中能够对应多个内核级线程,可是进程中的线程不和内核线程一一对应;这种线程模型会先建立多个内核级线程,而后用自身的用户级线程去对应建立的多个内核级线程,自身的用户级线程须要自己程序去调度,内核级的线程交给操做系统内核去调度。

Go语言的线程模型就是一种特殊的两级线程模型。暂且叫它“MPG”模型吧。

Go线程实现模型MPG

M指的是Machine,一个M直接关联了一个内核线程。
P指的是”processor”,表明了M所需的上下文环境,也是处理用户级代码逻辑的处理器。
G指的是Goroutine,其实本质上也是一种轻量级的线程。

三者关系以下图所示:

以上这个图讲的是两个线程(内核线程)的状况。一个M会对应一个内核线程,一个M也会链接一个上下文P,一个上下文P至关于一个“处理器”,一个上下文链接一个或者多个Goroutine。P(Processor)的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者经过运行时调用函数runtime.GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有固定数量的线程在运行go代码。Goroutine中就是咱们要执行并发的代码。图中P正在执行的Goroutine为蓝色的;处于待执行状态的Goroutine为灰色的,灰色的Goroutine造成了一个队列runqueues

三者关系的宏观的图为:

抛弃P(Processor)

你可能会想,为何必定须要一个上下文,咱们能不能直接除去上下文,让Goroutinerunqueues挂到M上呢?答案是不行,须要上下文的目的,是让咱们能够直接放开其余线程,当遇到内核线程阻塞的时候。

一个很简单的例子就是系统调用sysall,一个线程确定不能同时执行代码和系统调用被阻塞,这个时候,此线程M须要放弃当前的上下文环境P,以即可以让其余的Goroutine被调度执行。

如上图左图所示,M0中的G0执行了syscall,而后就建立了一个M1(也有可能自己就存在,没建立),(转向右图)而后M0丢弃了P,等待syscall的返回值,M1接受了P,将·继续执行Goroutine队列中的其余Goroutine

当系统调用syscall结束后,M0会“偷”一个上下文,若是不成功,M0就把它的Gouroutine G0放到一个全局的runqueue中,而后本身放到线程池或者转入休眠状态。全局runqueue是各个P在运行完本身的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,不然,全局runqueue上的goroutines可能得不到执行而饿死。

均衡的分配工做

按照以上的说法,上下文P会按期的检查全局的goroutine 队列中的goroutine,以便本身在消费掉自身Goroutine队列的时候有事可作。假如全局goroutine队列中的goroutine也没了呢?就从其余运行的中的P的runqueue里偷。

每一个P中的Goroutine不一样致使他们运行的效率和时间也不一样,在一个有不少P和M的环境中,不能让一个P跑完自身的Goroutine就没事可作了,由于或许其余的P有很长的goroutine队列要跑,得须要均衡。
该如何解决呢?

Go的作法倒也直接,从其余P中偷一半!

参考文献:
The Go scheduler《Go并发编程初版》

相关文章
相关标签/搜索