文档章节

对golang多核编程的一点了解

sysu_huyh5
 sysu_huyh5
发布于 2017/04/11 11:19
字数 2356
阅读 85
收藏 1
/*1、本博客的原创文章,都是本人平时学习所做的笔记,禁止转载,谢谢合作。
2、本博客的文章,未注明原创的,转载自其它博客或网站,作为自己的参考资料,感谢这些文章的原创人员。
3、如有侵犯您的知识产权和版权问题,请通知本人,本人会即时做出处理删除文章。
4、如果本博客的文章在知识点上有错误,欢迎指出错误所在,欢迎多多交流。谢谢!
5、本博客的目的是知识交流所用,他人在此留言并不代表本人同意、支持或者反对此观点。
6、本博客的文章、图片以及转载的文章引起的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其所造成的损失(包括因下载而感染电脑病毒),本人概不负责,亦不承担任何法律责任。
7、因本博客的文章、图片以及转载的文章所产生的任何法律(包括宪法,民法,刑法,书法,公检法,基本法,劳动法,婚姻法,输入法,没办法,国际法,今日说法,吸星大法,与台湾关系法及文中涉及或可能涉及、以及未涉及之法,各地治安管理条例)纠纷或责任本人概不负责。
8、一切网民在进入本博客的各个页面时,默认表示遵守本条款并完全同意。
9、此声明最终解释权归本人所有。
*/

 

 如何解决多核编程的问题,无非就是如何更好地屏蔽硬件上的复杂特性,例如缓存、一致性、内存屏障、原子操作等等,给开发者简单高效的并发特性。从这点出发,golang可以在内存中创建成千上万的协程,并且提供了协程间通信的基础设施,且golang自带汇编器和链接器。

 

    讲到golang的协程goroutine,就不得不提golang groutine和操作系统os的线程之间区别,便于我们理解golang底层并发的相关概念。

    1 栈的区别。每一个os线程都有一个固定大小的内存块(一般会是2MB)来做做栈,这个栈会用来存储当前os thread调用或挂起的函数的内部变量。这个固定大小的栈同时很大,又很小。很小来源于现在服务器机器内存基本都是几十个G。很大又可以理解成,如果golang里面goroutine也是使用如此固定大小的栈的话,那么成千上万个goroutine就不大可能了。除去大小的问题之外,固定大小的栈对于更复杂或者更深层次的递归函数调用来说显然是不够的。相反,golang的goroutine会以一个很小的栈开始其生命周期,一般只需要2KB,这个栈大小会根据需要动态地伸缩,其最大值能达到1GB。

    2 调度方式不同。os thread会被操作系统内核调度,每隔几毫秒,一个硬件计时器便会冲断处理器,调用一个叫做scheduler的内涵函数。这个函数会挂起当前执行的线程并保存内存中它寄存器内存,检查线程列表并决定下一次哪个线程可以被运行,并从内存中恢复该线程的寄存器信息,然后恢复执行该线程的现场并开始执行线程,这就是我们所谓的上下文切换过程,通常这个步骤是比较慢的(对于高性能要求来说),因为其局部性很差,需要几次内存访问,并且会增加运行的cpu周期。而golang的运行就包含了自己的调度器,这个调度器使用了一些技术手段,比如m:n调用,因为其会在n个操作系统线程上多工调用m个goroutine。和os的thread不同,go调度器并不是使用硬件定时器,goroutine调度并不需要进入内核上下文,所以goroutine调度比一个os thread调度代价要低得多。(稍后我们详细讲解下相关调度方案,并对比erlang调度方案)

        Go 的新调度器模型主要涉及3个核心概念:M、P及G,如下图所示:

    M 代表OS的线程,P代表当前系统的处理器数(一般由GOMAXPROCS 环境变量指定),G代表Go语言的用户级线程,也就是通常所说的 Goroutine。

    在多核平台上,P的数量一般对应处理器核心或硬件线程的数量,调度器需要保证所有的P都有G执行,以保证并行度。

M 必须与P绑定方能执行任务G,如下图所

 在旧版 Go 调度器实现中,由于缺少P, 一旦运行 G (goroutine)的 M (OS线程)陷入阻塞状态(如调用某个阻塞的系统调用)时,M 对应的 OS 线程就会被操作系统调度出去,从而导致系统中其他就绪的G也不能执行;而添加了P这个逻辑结构后,一旦发生上述情况,阻塞的 M 将被与其对应的 P 剥离,RUNTIME会再分配一个 M 并将其与已经剥离出来的 P 绑定,运行其他就绪的G。这个过程如下图所示:

 在实际实现中,考虑到代码执行的局部性因素,一般会倾向于推迟 M 与 P 剥离的时机。具体来说,RUNTIME中存在一个驻留的线程sysmon,用来监控所有进入Syscall 的 M,只有当 Syscall 执行时间超出某个阈值时,才会将 M 与 P 分离。

        另外一个保证系统运行稳定性的方式是负载均衡机制,在Go中,用了 “任务窃取” 的方法。

        首先介绍一下 Go 的任务队列,每个 P 都有一个私有的任务队列 (实现上是一个用数组表示的循环链表)以及一个公共队列(单链表表示),私有队列的功能是为了减轻公共队列的竞争开销。

    当一个 P 的私有任务队列为空时,它会从全局队列中寻找就绪态的 G 执行;如果全局队列也为空,则会随机选择窃取其他 P 私有执行队列中的任务G,从而保证所有线程尽可能以最大负载工作。其示意图如下:

  由于 P 的私有队列采用了数组结构,很容易计算出队列中间的位置,因此“窃取者” 采用了与 “被窃取者” 均分任务的方法,以尽可能达到负载均衡。

    无论从公共队列取任务还是进行“窃取”,都会引起一定的竞争开销,因此 RUNTIME 会倾向于将新建任务或新转变为就绪态的任务添加到当前执行 P 的私有队列中。 仅当执行的任务调用 yield 机制让出处理器或进入了一个长时间执行的系统调用时,该任务才会被添加到公共队列中。

    与 Go 不同,Erlang 的调度器是一个轮转而非协作式的调度器,每个进程创建时会被分配一个称为“reduction”的值,是一个计算量的度量(基本上等同于函数调用的次数),类似 OS 的时间片。进程每执行一定量的计算后,reduction值就会累计,一旦达到阈值,该进程就会给切换出去。这种调度方式在 Erlang 中被称为 “reduction-counting”。

        采用轮转的调度方式能更好的防止程序设计不当而导致的个别进程饿死的情况,同时能够实现更好的实时性功能。

        同时,Erlang还为进程提供了四个不同的优先级:max,high,normal和low。不同优先级进程按优先级调度;同级进程按轮转方式调度。每个调度器包含3个任务队列,Max和High具有单独的队列,normal和low则位于同一个队列 —— 调度器忽略一定次数的low级进程来实现二者间的差别。

        Erlang 调度器之所以能够实现优先级轮转调度,主要是得益于其基于虚拟机的执行方式:由于每条Erlang指令都需要经过 BEAM 解释执行,因此 process 的运行完全处于BEAM的监控之下,BEAM可以方便的完成对进程的切换。与之相对,由于 Go 的 Goroutine 与 RUNTIME 都是 Native 执行的,其在执行上的地位是平等的,RUNTIME 没有能力切换一个执行中的 Goroutine,除非其自己调出或调用RUNTIME 功在 ,因而只能实现协作调度。

 通过上述简单对比,我们大体上了解了Erlang和Go两种语言在并发任务调度上的异同,可以说二者各有优缺点:Go 的调度模型更加高效(Native)而 Erlang 则提供了更强大的功能(实时性、优先级)。

本文转载自:

共有 人打赏支持
sysu_huyh5
粉丝 6
博文 13
码字总数 13030
作品 0
深圳
程序员
Go中runtime.GOMAXPROCS的设置导致的性能问题

首先我们应该明确,并行和并发的区别,我之前文章中有详细的解释。概要说就是,并发一般都是被内核通过时间片或者中断来控制的,一旦遇到IO阻塞或者时间片用完,就会转移线程的使用权。单核不...

90design
06/29
0
0
Go 1.5 RC1 发布,8 月 20 日发布最终版!

Go 1.5rc1 发布,此版本是第一个候选版本,请帮忙测试并反馈:https://golang.org/issue/new 二进制包现已提供下载:https://golang.org/dl/#go1.5rc1 源代码下载:https://github.com/gola...

oschina
2015/08/06
4.3K
29
Go 1.5 Beta 3 发布,正式版将于 8 月中旬发布

Go 1.5 Beta 3 发布,Go 1.5 主要改进如下: 编译器和运行现在完全用 Go 写(带一点汇编)。 C 不再参与实施,所以曾经是构建分布必不可缺的 C,如今已经不见了。 垃圾收集器现在是 并发的 ...

oschina
2015/07/30
5.2K
34
为什么说 2017 年你必须要学习 Go 了

为什么要学习Go Go是未来的服务端语言— Tobias Lütke, Shopify。在过去的几年中,Golang逐步流行起来。 还有什么能比一门新语言让码农们疯狂呢? 因此,我开始学习了一段时间Golang,在这...

王练
2017/01/13
14.8K
120
Go 1.5.2 发布

Go 1.5.2 发布,更新内容请看:1095 commits 。 下载页面:go1.5.2 Go 1.5 主要改进: 编译器和运行现在完全用 Go 写(带一点汇编)。 C 不再参与实施,所以曾经是构建分布必不可缺的 C,如今...

oschina
2015/12/03
6.7K
47

没有更多内容

加载失败,请刷新页面

加载更多

arcgis jsapi接口入门系列(2):图层基础操作

//图层相关demo layerFun: function () { //获取地图的所有图层(不包括的图层类型:底图图层(basemaps)) let layers = this.map.layers; ...

canneljls
15分钟前
1
0
MySQL忘记root密码--不重启mysqd重置root密码

先提个问题:如何不重启mysqld,且没有权限修改用户账号和权限的情况下,如何重新设置root密码?不知道没关系,在此之前我也是不知道如何操作的,先看看下面的几种重置root密码的方法。 1、s...

IT--小哥
17分钟前
1
0
php7不再支持HTTP_RAW_POST_DATA,微信支付$GLOBALS[‘HTTP_RAW_POST_DATA’]获取不到数据,

升级到php7后, 发现旧的web系统有些问题, 查看后才发现原来是php7不再支持HTTP_RAW_POST_DATA 原来系统一些地方, 使用$GLOBALS[‘HTTP_RAW_POST_DATA’]来获取数据, 在php7里无法获取了 ...

15834278076
18分钟前
1
0
Android--拨打电话功能

Intent callIntent = new Intent(); callIntent.setAction(Intent.ACTION_DIAL); callIntent.setData(Uri.parse("tel:" + "4008823823")......

lanyu96
25分钟前
1
0
iOS多种刷新样式、音乐播放器、仿抖音视频、旅游App等源码

iOS精选源码 企业级开源项目,模仿艺龙旅行App 3D立体相册,可以旋转的立方体 横竖屏切换工具,使用陀螺仪检测手机设备方向,锁屏状... Swift版Refresh(可以自定义多种样式)架构方面有所优化...

Android爱开源
29分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部