你不得不知的Golang线程模型 [转载]
原著:翟陆续(加多) 资深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线程模型 [转载]的更多相关文章
- Hbase WAL线程模型源码分析
版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...
- GCD中有哪几种Queue?你自己建立过串行Queue吗?背后的线程模型是什么样的
一共有五种,看图 Paste_Image.png 主线程也就是那个main,一般后台处理数据就就用default那个.创建过一个queue,处理NSMutableArray的时候都在在这一个queue ...
- Android系统Surface机制的SurfaceFlinger服务的线程模型分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8062945 在前面两篇文章中,我们分析了Sur ...
- Memcached源码分析之线程模型
作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...
- GPU的线程模型和内存模型
遇见C++ AMP:在GPU上做并行计算 Written by Allen Lee I see all the young believers, your target audience. I see ...
- 【Netty源码分析】Reactor线程模型
1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...
- Go语言下的线程模型
阅读Go并发编程对go语言线程模型的笔记,解释的非常到,好记性不如烂笔头,忘记的时候回来翻一番,在此做下笔记. Go语言的线程实现模型,又3个必知的核心元素,他们支撑起了这个线程实现模型的主要框架: ...
- 从线程模型的角度看Netty的高性能
转载:Netty(二) 从线程模型的角度看 Netty 为什么是高性能的? 传统 IO 在 Netty 以及 NIO 出现之前,我们写 IO 应用其实用的都是用 java.io.* 下所提供的包. 比 ...
- java并发笔记之java线程模型
警告⚠️:本文耗时很长,先做好心理准备 java当中的线程和操作系统的线程是什么关系? 猜想: java thread —-对应-—> OS thread Linux关于操作系统的线程控制源码: ...
随机推荐
- Java8接口新特性
概述 Java 8中,你可以为接口添加静态方法和默认方法.从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念.猜想设计初衷可能使为了兼容8以下的jdk Java8出来了个函数 ...
- 数据库——SQL-SERVER练习(2)连接与子查询
一.实验准备 1.复制实验要求文件及“CREATE-TABLES.SQL”文件, 粘贴到本地机桌面. 2.启动SQL-SERVER服务. 3. 运行查询分析器, 点击菜单<文件>/< ...
- 【git】代码回退指定commit
[注意:如果提交的错误代码较少,可以在本地修改成 commit之前的正确代码样子,然后再提交一次即可.不用麻烦的操作回滚.] 开发人员错误将代码提交到gitlab的远程dev分支,回滚方法如下: 1. ...
- 基于 EntityFramework 生成 Repository 模式代码
借助 WeihanLi.EntityFramework 实现简单的 Repository Intro 很多时候一些简单的业务都是简单的增删改查,动态生成一些代码完成基本的增删改查,而这些增删改查代码大 ...
- 控制FlowDocumentScrollViewer滚动到最下方
原文发布于:https://www.chenxublog.com/2019/07/14/contrlo-flowdocumentscrollviewer-to-bottom.html 由于我在llco ...
- c# 字符串中全角和半角字符互转
public class ConvertDBCAndSBC { /// <summary>半角转成全角 /// 半角空格32,全角空格12288 /// 其他字符半角33~126,其他字符 ...
- Google浏览器出现崩溃问题解决
更新google浏览器79版本后所有页面出现崩溃情况,在试过加no-sandbox和兼容模式之后还是不太满意,后来搜到可能是网络问题,然后打开google浏览器安装文件夹,发现chrome_proxy ...
- fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer
问题:fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer 问题描述 通过mysql + sphinx ...
- Java关键字之abstract、final、static用法
abstract:即抽象的,可以修饰类.方法: 修饰类:当有一个方法为抽象方法时,这个类就是抽象类,抽象类不能被new,它是一个不完整的类. 修饰方法:这个方法就是抽象的,即只能方法的定义,没有方法的 ...
- xcode 运行出现的相应配置问题以及解决办法
在学习iOS开发的过程中,经常会在网上找一些demo学习,但是网上找的demo,在自己的机子上都会出各种各样的问题.下面我来整理一下,我所遇到的问题. 最近在接受一个比较老的混合开发的项目,出现了一下 ...