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

一、前言

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

二、三种线程模型

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

2.1 一对一模型

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

这种线程模型优点是在多处理器上,多个线程可以真正实现并行运行,并且当一个线程由于网络IO等原因被阻塞时候,其他的线程不受影响。

缺点是由于一般操作系统会限制内核线程的个数,所以用户线程的个数会受到限制。另外由于用户线程与系统线程一一对应,当用户线程比如执行Io操作(执行系统调用)时候,需要从用户态的用户程序的执行切换到内核态执行内核操作,然后等执行完毕后又会从内核态切换到用户态执行用户程序,而这个切换操作开销是相对比较大的。

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

2.2 多对一模型

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

这种模型好处是由于上下文切换在用户态,所以切换速度很快,开销很小;另外可创建的用户线程的数量可以很多,只受内存大小限制。

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

2.3 多对多模型

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

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

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

三、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)的数量则是由用户程序自己来确定,理论只要内存够大,可以无限制创建。

四、总结

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

你不得不知的Golang线程模型 [转载]的更多相关文章

  1. Hbase WAL线程模型源码分析

    版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...

  2. GCD中有哪几种Queue?你自己建立过串行Queue吗?背后的线程模型是什么样的

    一共有五种,看图 Paste_Image.png 主线程也就是那个main,一般后台处理数据就就用default那个.创建过一个queue,处理NSMutableArray的时候都在在这一个queue ...

  3. Android系统Surface机制的SurfaceFlinger服务的线程模型分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8062945 在前面两篇文章中,我们分析了Sur ...

  4. Memcached源码分析之线程模型

    作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...

  5. GPU的线程模型和内存模型

    遇见C++ AMP:在GPU上做并行计算 Written by Allen Lee I see all the young believers, your target audience. I see ...

  6. 【Netty源码分析】Reactor线程模型

    1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...

  7. Go语言下的线程模型

    阅读Go并发编程对go语言线程模型的笔记,解释的非常到,好记性不如烂笔头,忘记的时候回来翻一番,在此做下笔记. Go语言的线程实现模型,又3个必知的核心元素,他们支撑起了这个线程实现模型的主要框架: ...

  8. 从线程模型的角度看Netty的高性能

    转载:Netty(二) 从线程模型的角度看 Netty 为什么是高性能的? 传统 IO 在 Netty 以及 NIO 出现之前,我们写 IO 应用其实用的都是用 java.io.* 下所提供的包. 比 ...

  9. java并发笔记之java线程模型

    警告⚠️:本文耗时很长,先做好心理准备 java当中的线程和操作系统的线程是什么关系? 猜想: java thread —-对应-—> OS thread Linux关于操作系统的线程控制源码: ...

随机推荐

  1. 黄聪:Mysql开启InnoDB引擎出现1067错误的解决办法

    在my.ini文件添加下面这行: innodb_force_recovery = 1 可以解决:InnoDB: Attempted to open a previously opened tables ...

  2. 英语阅读——Speaking Chinese in America

    这篇文章是<新视野大学英语>第四册的第五单元的文章,第一遍英语阅读完后对比中文,发现自己对作者的观点理解有些出入.作者反对的是认为中国说话客套而美国人直接的观点,利用自己的经历表达了中文也 ...

  3. 你见过的最全面的 Python 重点

    由于总结了太多的东西,所以篇幅有点长,这也是我"缝缝补补"总结了好久的东西. Py2 VS Py3 print成为了函数,python2是关键字 不再有unicode对象,默认st ...

  4. JVM 参数配置

    JAVA_OPTS="-server -Xms2048m -Xmx2048m -Xmn512m -Xss256k -XX:PermSize=256m -XX:MaxPermSize=256m ...

  5. PHP mysqli_kill MySQLi 函数

    mysqli_kill - 让服务器杀掉一个 MySQL 线程 语法:mysqli_kill ( mysqli $link , int $processid ) 本函数可以用来让服务器杀掉 proce ...

  6. ES6变量的解构赋值(二)对象的解构赋值

    前面我们知道,数组的结构赋值需要按顺序进行赋值, let [a,,c] = [,,] console.log(a); console.log(c);//3 let [a,b] = [1];consol ...

  7. 跳转常规 -- 为什么不要使用404、500等http状态码作为业务代码响应

    不要使用HTTP状态码作为业务代码响应 HTTP状态码 是指浏览器访问网址,成功或异常时浏览器或服务器告知用户的代码数字,它指的是网络或服务器的状态,有着不同的含义. 参加MDN.Java Api,常 ...

  8. zabbix4.0搭建2

    server端(ip 192.168.200.15) proxy端(ip 192.168.200.22) agent端(ip 192.168.200.12) server端: #安装数据库 [mari ...

  9. Python继承、多继承、魔术方法

    继承和多继承的概念和使用 super的用法 __str__ __repr__ __call__ 多继承方法解析顺序和Mix-in开发模式 魔术方法原理和作用 继承 定义类的时候,在类名后面的括号里填继 ...

  10. K60时钟分析

    转载:https://blog.csdn.net/hcx25909/article/details/7164650 1.飞思卡尔K60时钟系统          飞思卡尔K60时钟系统如上图所示,可以 ...