Go语言并发
Go语言并发机制初探
Go 语言相比Java等一个很大的优势就是可以方便地编写并发程序。Go 语言内置了 goroutine 机制,使用goroutine可以快速地开发并发程序, 更好的利用多核处理器资源。这篇文章学习 goroutine 的应用及其调度实现。
一、Go语言对并发的支持
使用goroutine编程
使用 go 关键字用来创建 goroutine 。将go声明放到一个需调用的函数之前,在相同地址空间调用运行这个函数,这样该函数执行时便会作为一个独立的并发线程。这种线程在Go语言中称作goroutine。
goroutine的用法如下:
1
2
3
4
5
6
7
8
9
10
11
|
//go 关键字放在方法调用前新建一个 goroutine 并执行方法体 go GetThingDone(param1, param2); //新建一个匿名方法并执行 go func (param1, param2) { }(val1, val2) //直接新建一个 goroutine 并在 goroutine 中执行代码块 go { //do someting... } |
因为 goroutine 在多核 cpu 环境下是并行的。如果代码块在多个 goroutine 中执行,我们就实现了代码并行。
如果需要了解程序的执行情况,怎么拿到并行的结果呢?需要配合使用channel进行。
使用Channel控制并发
Channels用来同步并发执行的函数并提供它们某种传值交流的机制。
通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。
可以使用内置函数 make分配一个channel:
1
2
3
4
5
|
i := make( chan int) // by default the capacity is 0 s := make( chan string, 3) // non-zero capacity r := make(<- chan bool) // can only read from w := make( chan <- []os.FileInfo) // can only write to |
配置runtime.GOMAXPROCS
使用下面的代码可以显式的设置是否使用多核来执行并发任务:
1
|
runtime.GOMAXPROCS() |
GOMAXPROCS的数目根据任务量分配就可以,但是不要大于cpu核数。
配置并行执行比较适合适合于CPU密集型、并行度比较高的情景,如果是IO密集型使用多核的化会增加cpu切换带来的性能损失。
了解了Go语言的并发机制,接下来看一下goroutine 机制的具体实现。
二、区别并行与并发
进程、线程与处理器
在现代操作系统中,线程是处理器调度和分配的基本单位,进程则作为资源拥有的基本单位。每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成。线程是进程内部的一个执行单元。 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。 用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。
并行与并发
并行与并发(Concurrency and Parallelism)是两个不同的概念,理解它们对于理解多线程模型非常重要。
在描述程序的并发或者并行时,应该说明从进程或者线程的角度出发。
并发:一个时间段内有很多的线程或进程在执行,但何时间点上都只有一个在执行,多个线程或进程争抢时间片轮流执行
并行:一个时间段和时间点上都有多个线程或进程在执行
非并发的程序只有一个垂直的控制逻辑,在任何时刻,程序只会处在这个控制逻辑的某个位置,也就是顺序执行。如果一个程序在某一时刻被多个CPU流水线同时进行处理,那么我们就说这个程序是以并行的形式在运行。
并行需要硬件支持,单核处理器只能是并发,多核处理器才能做到并行执行。
并发是并行的必要条件,如果一个程序本身就不是并发的,也就是只有一个逻辑执行顺序,那么我们不可能让其被并行处理。
并发不是并行的充分条件,一个并发的程序,如果只被一个CPU进行处理(通过分时),那么它就不是并行的。
举一个例子,编写一个最简单的顺序结构程序输出"Hello World",它就是非并发的,如果在程序中增加多线程,每个线程打印一个"Hello World",那么这个程序就是并发的。如果运行时只给这个程序分配单个CPU,这个并发程序还不是并行的,需要部署在多核处理器上,才能实现程序的并行。
三、几种不同的多线程模型
用户线程与内核级线程
线程的实现可以分为两类:用户级线程(User-LevelThread, ULT)和内核级线程(Kemel-LevelThread, KLT)。用户线程由用户代码支持,内核线程由操作系统内核支持。
多线程模型
多线程模型即用户级线程和内核级线程的不同连接方式。
(1)多对一模型(M : 1)
将多个用户级线程映射到一个内核级线程,线程管理在用户空间完成。 此模式中,用户级线程对操作系统不可见(即透明)。
优点: 这种模型的好处是线程上下文切换都发生在用户空间,避免的模态切换(mode switch),从而对于性能有积极的影响。
缺点:所有的线程基于一个内核调度实体即内核线程,这意味着只有一个处理器可以被利用,在多处理器环境下这是不能够被接受的,本质上,用户线程只解决了并发问题,但是没有解决并行问题。如果线程因为 I/O 操作陷入了内核态,内核态线程阻塞等待 I/O 数据,则所有的线程都将会被阻塞,用户空间也可以使用非阻塞而 I/O,但是不能避免性能及复杂度问题。
(2) 一对一模型(1:1)
将每个用户级线程映射到一个内核级线程。
每个线程由内核调度器独立的调度,所以如果一个线程阻塞则不影响其他的线程。
优点:在多核处理器的硬件的支持下,内核空间线程模型支持了真正的并行,当一个线程被阻塞后,允许另一个线程继续执行,所以并发能力较强。
缺点:每创建一个用户级线程都需要创建一个内核级线程与其对应,这样创建线程的开销比较大,会影响到应用程序的性能。
(3)多对多模型(M : N)
内核线程和用户线程的数量比为 M : N,内核用户空间综合了前两种的优点。
这种模型需要内核线程调度器和用户空间线程调度器相互操作,本质上是多个线程被绑定到了多个内核线程上,这使得大部分的线程上下文切换都发生在用户空间,而多个内核线程又可以充分利用处理器资源。
四、goroutine机制的调度实现
goroutine机制实现了M : N的线程模型,goroutine机制是协程(coroutine)的一种实现,golang内置的调度器,可以让多核CPU中每个CPU执行一个协程。
理解goroutine机制的原理,关键是理解Go语言scheduler的实现。
调度器是如何工作的
Go语言中支撑整个scheduler实现的主要有4个重要结构,分别是M、G、P、Sched, 前三个定义在runtime.h中,Sched定义在proc.c中。
Sched结构就是调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。
M结构是Machine,系统线程,它由操作系统管理的,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
P结构是Processor,处理器,它的主要用途就是用来执行goroutine的,它维护了一个goroutine队列,即runqueue。Processor是让我们从N:1调度到M:N调度的重要部分。
G是goroutine实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine很重要的信息,例如其阻塞的channel。
Processor的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有GOMAXPROCS个线程在运行go代码。
参考这篇传播很广的博客:http://morsmachine.dk/go-scheduler
我们分别用三角形,矩形和圆形表示Machine Processor和Goroutine。
在单核处理器的场景下,所有goroutine运行在同一个M系统线程中,每一个M系统线程维护一个Processor,任何时刻,一个Processor中只有一个goroutine,其他goroutine在runqueue中等待。一个goroutine运行完自己的时间片后,让出上下文,回到runqueue中。 多核处理器的场景下,为了运行goroutines,每个M系统线程会持有一个Processor。
在正常情况下,scheduler会按照上面的流程进行调度,但是线程会发生阻塞等情况,看一下goroutine对线程阻塞等的处理。
线程阻塞
当正在运行的goroutine阻塞的时候,例如进行系统调用,会再创建一个系统线程(M1),当前的M线程放弃了它的Processor,P转到新的线程中去运行。
runqueue执行完成
当其中一个Processor的runqueue为空,没有goroutine可以调度。它会从另外一个上下文偷取一半的goroutine。
五、对并发实现的进一步思考
Go语言的并发机制还有很多值得探讨的,比如Go语言和Scala并发实现的不同,Golang CSP 和Actor模型的对比等。
了解并发机制的这些实现,可以帮助我们更好的进行并发程序的开发,实现性能的最优化。
关于三种多线程模型,可以关注一下Java语言的实现。
我们知道Java通过JVM封装了底层操作系统的差异,而不同的操作系统可能使用不同的线程模型,例如Linux和windows可能使用了一对一模型,solaris和unix某些版本可能使用多对多模型。JVM规范里没有规定多线程模型的具体实现,1:1(内核线程)、N:1(用户态线程)、M:N(混合)模型的任何一种都可以。谈到Java语言的多线程模型,需要针对具体JVM实现,比如Oracle/Sun的HotSpot VM,默认使用1:1线程模型。
Go语言并发的更多相关文章
- Go语言并发编程示例 分享(含有源代码)
GO语言并发示例分享: ppt http://files.cnblogs.com/files/yuhan-TB/GO%E8%AF%AD%E8%A8%80.pptx 代码, 实际就是<<Go ...
- Go语言并发与并行学习笔记(三)
转:http://blog.csdn.net/kjfcpua/article/details/18265475 Go语言并发的设计模式和应用场景 以下设计模式和应用场景来自Google IO上的关于G ...
- (转)Go语言并发模型:使用 context
转载自:https://segmentfault.com/a/1190000006744213 context golang 简介 在 Go http包的Server中,每一个请求在都有一个对应的 g ...
- Go语言 并发编程
Go语言 并发编程 作者:Eric 微信:loveoracle11g 1.创建goroutine // 并行 是两个队列同时使用两台咖啡机 // 并发 是两个队列交替使用一台咖啡机 package m ...
- 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
2007年诞生的Go语言,凭借其近C的执行性能和近解析型语言的开发效率,以及近乎完美的编译速度,席卷全球.Go语言相关书籍也如雨后春笋般涌现,前不久,一本名为<Go语言并发之道>的书籍被翻 ...
- go语言之进阶篇并行和并发的区别与go语言并发优势
1.并行和并发的概念 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行. 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在 ...
- 09. Go 语言并发
Go 语言并发 并发指在同一时间内可以执行多个任务.并发编程含义比较广泛,包含多线程编程.多进程编程及分布式程序等.本章讲解的并发含义属于多线程编程. Go 语言通过编译器运行时(runtime),从 ...
- MIT 6.824学习笔记3 Go语言并发解析
之前看过一个go语言并发的介绍:https://www.cnblogs.com/pdev/p/10936485.html 但这个太简略啦.下面看点深入的 还记得https://www.cnblog ...
- Go语言-并发模式-资源池实例(pool)
Go语言并发模式 利用goroutine和channel进行go的并发模式,实现一个资源池实例(<Go语言实战>书中实例稍作修改) 资源池可以存储一定数量的资源,用户程序从资源池获取资源进 ...
随机推荐
- Android Java Mail与Apache Mail发送邮件对比
原文链接: 一.邮件简介 一封邮件由很多信息构成,主要的信息如下,其他的暂时不考虑,例如抄送等: 1.收件人:收件人的邮箱地址,例如xxx@xx.com 2.收件人姓名:大部分的邮件显示时都会显 ...
- 【习题 3-1 UVA - 1585】Score
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 模拟水题 [错的次数] 在这里输入错的次数 [反思] 在这里输入反思 [代码] #include <bits/stdc++.h ...
- client产生CLOSE_WAIT状态的解决方式
现象 生产环境和測试环境都发现有个外围应用通过搜索服务调用搜索引擎时.偶尔会出现大量的訪问超时的问题,通过例如以下方式进行分析排查: l 首先是拿到搜索服务的JavaCore.发现其堵在HttpCli ...
- java并发之生产者消费者模型
生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者 ...
- [TypeStyle] Load raw CSS in TypeStyle
TypeStyle tries to be an all in one CSS in JS management solution so you can always fall back to raw ...
- angular管道相关知识
原文地址 https://www.jianshu.com/p/22e0f95bcf24 什么是管道 每个应用开始的时候差不多都是一些简单任务:获取数据.转换它们,然后把它们显示给用户. 获取数据可能简 ...
- MySQL5.7 四种日志文件
mysql 日志包括:错误日志,二进制日志,通用查询日志,慢日志等 一:通用查询日志: 记录建立的客户端连接和执行的语句 1)show variables like '%verision%'; 显示数 ...
- 为什么位运算可以实现加法(1、 不考虑进位的情况下位运算符中的异或^可以表示+号)(2、 位运算符中的与运算符&和左移运算符<<可以模拟加法中的进位)(3、位运算不仅可以做加法,还可以做其它的乘法减法等:计算机本质是二进制运算)
为什么位运算可以实现加法(1. 不考虑进位的情况下位运算符中的异或^可以表示+号)(2. 位运算符中的与运算符&和左移运算符<<可以模拟加法中的进位)(3.位运算不仅可以做加法,还 ...
- 广播(broadcast)、电视与电视网络
1. 闭路电视与电视 Closed Circuit Television (CCTV) 一种图像通信系统.其信号从源点只传给预先安排好的与源点相通的特定电视机.广泛用于大量不同类型的监视工作.教育.电 ...
- 【35.43%】【hdu 4347】The Closest M Points
Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 98304/98304 K (Java/Others) Total Submissio ...