你不得不知的Golang线程模型 [转载]

原著:翟陆续(加多) 资深Java , 著Java并发编程之美编程

1、前言

本节咱们来探讨Go的线程模型,首先咱们先来回顾下常见的三种线程模型,而后在介绍Go中独特的线程模型。网络

2、三种线程模型

线程的并发执行是有操做系统来进行调度的,操做系统通常都都在内核提供对线程的支持。而咱们在使用高级语言编写程序时候建立的线程是用户线程,那么用户线程与内核线程是什么关系那?其实下面将要讲解的三种线程模型就是根据用户线程与内核线程关系的不一样而划分的。多线程

2.1 一对一模型

这种线程模型下用户线程与内核线程是一一对应的,当从程序入口点(好比main函数)启动后,操做系统就建立了一个进程,这个main函数所在的线程就是主线程,在main函数内当咱们使用高级语言建立一个用户线程的时候,其实对应建立了一个内核线程,以下图:并发

这种线程模型优势是在多处理器上,多个线程能够真正实现并行运行,而且当一个线程因为网络IO等缘由被阻塞时候,其余的线程不受影响。函数

缺点是因为通常操做系统会限制内核线程的个数,因此用户线程的个数会受到限制。另外因为用户线程与系统线程一一对应,当用户线程好比执行Io操做(执行系统调用)时候,须要从用户态的用户程序的执行切换到内核态执行内核操做,而后等执行完毕后又会从内核态切换到用户态执行用户程序,而这个切换操做开销是相对比较大的。操作系统

另外这里提下高级语言Java的线程模型就是使用的这种一对一的模型,因此Java中多线程对共享变量使用锁同步时候会致使获取锁失败的线程进行上下文切换,而JUC包提供的无锁CAS操做则不会产生上下文切换。线程

2.2 多对一模型

多对一模型是指多个用户线程对应一个内核线程,同时同一个用户线程只能对应一个内核线程,这时候对应同一个内核线程的多个用户线程的上下文切换是由用户态的运行时线程库来作的,而不是由操做系统调度系统来作的,其模型以下:debug

这种模型好处是因为上下文切换在用户态,因此切换速度很快,开销很小;另外可建立的用户线程的数量能够不少,只受内存大小限制。协程

这种模型因为多个用户线程对应一个内核线程,当该内核线程对应的一个用户线程被阻塞挂起时候,该内核线程对应的其余用户线程也不能运行了,由于这时候内核线程已经被阻塞挂起了。另外这种模型并不能很好的利用多核CPU进行并发运行。blog

2.3 多对多模型

多对多模型则结合一对一和多对一模型的特色,让大量的用户线程对应少数几个内核线程上,其模型图以下:

这时候每一个内核线程对应多个用户线程,每一个用户线程有能够对应多个内核线程,当一个用户线程阻塞后,其对应的当前的内核线程会被阻塞,可是被阻塞的内核线程对应的其余用户线程能够切换到其余的内核线程上继续运行,因此多对多模型是能够充分利用多核CPU提高运行效能的。

另外多对多模型也对用户线程个数没有限制,理论上只要内存够用能够无限建立。

3、Go线程模型

Go线程模型属于多对多线程模型,其模型以下

Go中使用使用go语句建立的goroutine能够认为是轻量级的用户线程,go线程模型包含三个概念:内核线程(M),goroutine(G),逻辑处理器(P),在Go中每一个逻辑处理器(P)会绑定到某一个内核线程上,每一个逻辑处理器(P)内有一个本地队列,用来存放go运行时分配的goroutine。在上面介绍的多对多线程模型中是操做系统调度线程在物理CPU上运行,在Go中则是Go的运行时调度goroutine在逻辑处理器(P)上运行。

在go中存在两级调度,一级是操做系统的调度系统,该调度系统调度逻辑处理器占用cpu时间片运行,一级是go的运行时调度系统,该调度系统调度某个goroutine在逻辑处理上运行。

使用go语句建立一个goroutine后,建立的goroutine会被放入go运行时调度器的全局运行队列中,而后go运行时调度器会把全局队列中的goroutine分配给不一样的逻辑处理器(P),分配的goroutine会被放到逻辑处理器(P)的本地队列中,当本地队列中某个goroutine就绪后待分配到时间片后就能够在逻辑处理器上运行了,如上图goroutine1当前正在占用逻辑处理器1运行。

须要注意的是为了不某些goroutine出现饥饿现象,被分配到某一个逻辑处理器(P)上的多个goroutine是分时在该逻辑处理器运行的,而不是独占运行直到结束,好比每一个goroutine从开始到运行结束须要10分钟,那么当前逻辑处理器下的goroutine1,goroutine2,goroutine3,并非顺序执行,而是交叉并发运行的。

goroutine内部实现与在多个操做系统线程(Os 线程)之间复用的协程(coroutines)同样。若是一个goroutine阻塞OS线程,例如等待输入,则该OS线程对应的逻辑处理器P中的其余goroutine将迁移到其余OS线程,以便它们能够继续运行

如上图左侧假设goroutine1在执行文件文件读取操做,则goroutine1会致使内核线程1阻塞,这时候go运行时调度器会把goroutine1所在的逻辑处理器1迁移到其余的内核线程上(这里是内核线程2上),这时候逻辑处理器1上的goroutine2和goroutine3就不会受goroutine1的影响了。等goroutine1文件读取操做完成后goroutine1又会被go运行时调度系统从新放入到逻辑处理器1的本地队列。

须要注意的是go运行时内核线程(M)的数量默认是10000个,你可使用runtime/debug包里面的debug.SetMaxThreads(10000)来设置。

默认状况下,Go默认是给每一个可用的物理处理器都分配一个逻辑处理器(p),若是你须要修改逻辑处理器(P)个数可使用runtime包的runtime.GOMAXPROCS函数设置.

至于goroutine(G)的数量则是由用户程序本身来肯定,理论只要内存够大,能够无限制建立。

4、总结

本节咱们探讨了go的线程模型,讲解了Go中是多对多的线程模型,正是因为这种线程模型才让go中每台机器能够建立成千上万的goroutine(轻量级线程),了解了go的线程模型,特别是其中的MPG概念,就能够随业务须要动态设置最优方案。

相关文章
相关标签/搜索