Go并发原理
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并发模型,是通过goroutine
和channel
来实现的。
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)
你可能会想,为什么一定需要一个上下文,我们能不能直接除去上下文,让Goroutine
的runqueues
挂到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并发编程第一版》
Go并发原理的更多相关文章
- MySQL InnoDB 实现高并发原理
MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...
- nginx、swoole高并发原理初探
阅前热身 为了更加形象的说明同步异步.阻塞非阻塞,我们以小明去买奶茶为例. 同步与异步 同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式. 同步:当一个同步调用发出去后,调用者要一直等待调 ...
- InnoDB高并发原理
一.并发控制 为啥要进行并发控制? 并发的任务对同一个临界资源进行操作,如果不采取措施,可能导致不一致,故必须进行并发控制(Concurrency Control). 技术上,通常如何进行并发控制? ...
- 【科普】MySQL中DDL操作背后的并发原理
一. 简介 DQL:指数据库中的查询(select)操作. DML:指数据库中的插入(insert).更新(update).删除(delete)等行数据变更操作. DDL:指数据库中加列(add co ...
- 【多线程与高并发原理篇:4_深入理解synchronized】
1. 前言 越是简单的东西,在深入了解后发现越复杂.想起了曾在初中阶段,语文老师给我们解说<论语>的道理,顺便给我们提了一句,说老子的无为思想比较消极,学生时代不要太关注.现在有了一定的生 ...
- Netty高并发原理
Netty是一个高性能 事件驱动的异步的非堵塞的IO(NIO)框架,用于建立TCP等底层的连接,基于Netty可以建立高性能的Http服务器.支持HTTP. WebSocket .Protob ...
- 理论铺垫:阻塞IO、非阻塞IO、IO多路复用/事件驱动IO(单线程高并发原理)、异步IO
完全来自:http://www.cnblogs.com/alex3714/articles/5876749.html 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同 ...
- Node单线程高并发原理
一.node是如何处理web请求的 浏览器中的js是单线程的,node也是单线程的.这个单线程相当于一个大管家,一切大小事务都要经过他的手才能办成,它总是把IO任务放入到任务池中. 虽然说是单线程,但 ...
- Golang并发原理及GPM调度策略(一)
其实从一开始了解到go的goroutine概念就应该想到,其实go应该就是在内核级线程的基础上做了一层逻辑上的虚拟线程(用户级线程)+ 线程调度系统,如此分析以后,goroutine也就不再那么神秘了 ...
随机推荐
- 51nod 1625 贪心/思维
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1625 1625 夹克爷发红包 基准时间限制:1 秒 空间限制:13107 ...
- TCP/IP详解学习笔记(4)-ICMP协议,ping和Traceroute【转】
转自:http://blog.csdn.net/goodboy1881/article/details/670761 1.IMCP协议介绍 前面讲到了,IP协议并不是一个可靠的协议(是一种尽力传送的协 ...
- inndb索引原理
###详解b+树如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含 ...
- Arc066_F Contest with Drinks Hard
传送门 题目大意 有一个长为$N$的序列$A$,你要构造一个长为$N$的$01$序列使得$01$序列全部由$1$组成的子串个数$-$两个序列的对应位置两两乘积之和最大,每次独立的询问给定$pos,x$ ...
- BZOJ - 4196 软件包管理器 (树链剖分+dfs序+线段树)
题目链接 设白色结点为未安装的软件,黑色结点为已安装的软件,则: 安装软件i:输出结点i到根的路径上的白色结点的数量,并把结点i到根的路径染成黑色.复杂度$O(nlog^2n)$ 卸载软件i:输出结点 ...
- yum search/intall, Error: xz compression not available
转自:http://blog.hexu.org/archives/2060.shtml 遇到这个问题情景: 下午第一台系统是Centos7, 安装配置完成后,接着一台是Centos 6 系统,由于疏忽 ...
- Cash Machine(多重背包二进制转换)
个人心得:多重背包,自己根据转换方程写总是TLE,后面去网上看了二进制转换,不太理解: 后面仔细想了下,用自己的思想理解下把,就是将对应number,cash总和用二进制拆分, 然后全部装入到一个数组 ...
- RabbitMQ集群 Docker一键部署
以下内容来自网络转载 步骤1. 安装docker 以centos7为例,https://docs.docker.com/engine/installation/linux/centos/ 步骤2. 创 ...
- PHP怎么把经过UTF-8编码的中文字符转换成正常的中文
问题的场景: html 为utf-8编码<meta http-equiv="Content-Type" content="text/html; charset=UT ...
- 2、Monkey简单使用
1.使用Monkey测试,前提是有虚拟机或者真机设备,查看是否有设备存在:adb devices (需要先进入SDK的tool目录下才执行该操作) 2.查看设备上各个包名 adb shell pm l ...