文档章节

对golang多核编程的一点了解

sysu_huyh5
 sysu_huyh5
发布于 2017/04/11 11:19
字数 2356
阅读 94
收藏 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
济南技术沙龙-1314跨年活动

最近这几年,软件开发语言可谓是层出不穷。在这些新的编程语言中,最多的就是函数式语言,而这个趋势也正是顺应了现代计算机平台的一个重大改变——多核技术。 多核时代语言流派一分为二:一...

缪斯的情人
2013/12/30
2.6K
45
golang VS php 性能对比

原文 http://www.isno.cn/2013/12/golang-vs-php/ 在工作的项目中,我的主要开发语言是php,因需求或者为弥补php的缺陷,需要为php做各种各样的扩展,比如php本身不支持线程,没有队列,进程也...

神仙
2013/12/19
28.2K
34
为什么说 2017 年你必须要学习 Go 了

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

王练
2017/01/13
16K
121
Go语言规范(介绍)

Google发布Go语言已经有一段时间了,但是还少有在论坛看到关于Go的讨论,几个Go语言的BBS也冷冷清清的,国内程序员对 Go语言 似乎还持观望态度。在看了它的一点特性和一段时间的试用之后,我...

曾赛
2010/05/16
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

添加并发

<!-- 认购线程池,支持最大并发送10 最大排队队列为一万--> <task:executor id="investBuyPool" pool-size="1-10" queue-capacity="10000"/> 方法添加注解@Async("investBuyPool")......

一夜
17分钟前
2
0
Gensim?

Gensim是一款开源的第三方Python工具包,用于从原始的非结构化的文本中,无监督地学习到文本隐层的主题向量表达。它支持包括TF-IDF,LSA,LDA,和word2vec在内的多种主题模型算法,支持流式训...

火力全開
30分钟前
3
0
图片验证码

图片验证码,线上登录是最常见的场景,也有发送手机验证码之前进行图片验证码的验证。 图片验证码很大程度上防止机器人撞库登录或频繁发起短信验证码从而使系统暂时性"瘫痪"。 以下是项目中用...

貔貅叔
34分钟前
4
0
科班出身程序员和培训出来的程序员区别在哪?

科班出身只是代表你要从事的职业和你的专业是一致的,代表着你具备了得天独厚的优势,至于是不是优势看你上班好不好学,平时逃课没,上班是认真听还是玩手机。一般而言科班出来的理论性比较强...

java菜分享
34分钟前
4
0
java框架学习日志-8(AOP简介)

小王被委托开发一款游戏,程序分为启动页面,登陆页面,战斗页面等。小王就采用了面向对象编程思想(OOP),把整个程序分解成下图 这种就是传统的自上而下的编程,或者说纵向的编程,负责启动...

白话
34分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部