golang goroutine的调度

1、什么是协程?

    协程是一种用户态的轻量级线程。

2、进程、线程、协程的关系和区别:

    * 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

    * 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

    * 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

    * 协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能。

    * 执行协程只需要极少的栈内存(大概是4~5KB),默认情况下,线程栈的大小为1MB。

        goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。

        runtime。GOMAXPROCS(runtime。NumCPU()) // go version>=1.5的时候,GOMAXPROCS的默认值就是go程序启动时可见的操作系统认为的CPU个数。

        注意:在go程序中使用的操作系统线程数量包括:正服务于cgo calls的线程,阻塞于操作系统calls的线程,所以go程序中使用的操作系统线程数量可能大于GOMAXPROCS的值。

3、调度

    要理解协程的实现,首先需要了解go中的三个非常重要的概念,它们分别是G(goroutine)、M(machine)和P(process):

    G (goroutine)

        G是goroutine的头文字,goroutine可以解释为受管理的轻量线程,goroutine使用go关键词创建。

        举例来说,func main() { go other() },这段代码创建了两个goroutine,一个是main,另一个是other,注意main本身也是一个goroutine。

        goroutine的新建,休眠,恢复,停止都受到go运行时的管理。

        goroutine执行异步操作时会进入休眠状态,待操作完成后再恢复,无需占用系统线程。

        goroutine新建或恢复时会添加到运行队列,等待M取出并运行。

    M (machine)

        M是machine的头文字,在当前版本的golang中等同于系统线程。

        M可以运行两种代码:

            * go代码,即goroutine,M运行go代码需要一个P。

            * 原生代码,例如阻塞的syscall,M运行原生代码不需要P。

        M会从运行队列中取出G,然后运行G,如果G运行完毕或者进入休眠状态,则从运行队列中取出下一个G运行,周而复始。

        有时候G需要调用一些无法避免阻塞的原生代码,这时M会释放持有的P并进入阻塞状态,其他M会取得这个P并继续运行队列中的G。

        go需要保证有足够的M可以运行G,不让CPU闲着,也需要保证M的数量不能过多。

    P (process)

        P是process的头文字,代表M运行G所需要的资源。

        虽然P的数量默认等于cpu核心数,但可以通过环境变量GOMAXPROC修改,在实际运行时P跟cpu核心并无任何关联。       

        P也可以理解为控制go代码的并行度的机制,

            * 如果P的数量等于1,代表当前最多只能有一个线程(M)执行go代码;

            * 如果P的数量等于2,代表当前最多只能有两个线程(M)执行go代码。

        执行原生代码的线程数量不受P控制。

        因为同一时间只有一个线程(M)可以拥有P,P中的数据都是锁自由(lock free)的,读写这些数据的效率会非常的高。

    备注:每个P会维护一个本地的运行队列,除了每个P拥有一个本地的运行队列外,还存在一个全局的运行队列。

    为什么需要创造一个用户空间调度器?

        POSIX线程API是对现有Unix进程模型的一个非常大的逻辑扩展,而且线程获得了非常多的跟进程相同的控制。

        比如,线程有它自己的信号掩码,线程能够被赋予CPU affinity功能(就是指定线程只能在某个CPU上运行),

        线程能被添加到[cgroups]中,线程所用到的资源也可以被查询到。

        所有的这些控制增大了Go程序使用goroutines时根本不需要的特性的开销,当你的程序有100,000个线程的时候,这些开销会急剧增长。

        另外一个问题是,基于Go模型,操作系统不能给出特别好的决策。

        比如,当运行一次垃圾收集的时候,Go的垃圾收集器要求所有线程都被停止而且要求内存要处于一致状态。

        这个涉及到要等待全部运行时线程到达一个点,系统事先知道在这个点内存是一致的。

        当很多被调度的线程分散在随机的点上的时候,结果就是你不得不等待他们中的大多数到达一致状态。

        Go调度器能够作出这样的决策,就是只在内存保持一致的点上进行调度。

        这就意味着,当程序为垃圾收集而停止的时候,程序只须等待在一个CPU核上处于活跃运行状态的线程即可。

    目前有三个常见的线程模型:

        一个是N:1的,即多个用户空间线程运行在一个OS线程上。这个模型可以很快的进行上下文切换,但是不能利用多核系统(multi-core systems)的优势。

        另一个模型是1:1的,即可执行程序的一个线程匹配一个OS线程。这个模型能够利用机器上的所有核心的优势,但是上下文切换非常慢,因为它不得不陷入OS(trap through the OS)。

        Go试图通过M:N的调度器去获取这两个世界的全部优势。它在任意数目的OS线程上调用任意数目的goroutines。你可以快速进行上下文切换,并且还能利用你系统上所有的核心的优势。这个模型主要的缺点是它增加了调度器的复杂性。

    原理:

        P的数量在初始化由GOMAXPROCS决定;

        程序要做的就是添加G;

        G的数量超出了M的处理能力,且还有空余P的话,runtime就会自动创建新的M;

        M拿到P后才能干活,取G的顺序:本地运行队列 > 全局运行队列 > 其他P的运行队列,如果所有运行队列都没有可用的G,M会归还P并进入休眠。

        一个G如果发生阻塞等事件会进行阻塞,G发生上下文切换条件:

            * 系统调用;

            * 读写channel;

            * gosched主动放弃,会将G扔进全局队列;

        一个G发生阻塞时,M0让出P,由M1接管其任务队列;当M0执行的阻塞调用返回后,再将G0扔到全局队列,自己则进入睡眠(因为没有P,无法干活)。

        例子:

            当G0调用一个系统调用的时候。因为一个线程不能既执行代码同时又阻塞到一个系统调用上,则需要移交对应于这个线程的P以让这个P可以被调度。

            M0放弃了它的P以保证其它的M1可以运行它。调度器确保有足够的线程来运行所有的P。

            M1可能仅仅是系统为了处理G0的系统调用而被创建出来,或者它可能来自一个线程池。

            这个处于系统调用中的线程M0将会保持在这个导致系统调用的G0上,因为从技术上来说,它仍然在执行,虽然阻塞在OS里了。

            当这个系统调用返回的时候,这个线程必须尝试获取一个P1来运行这个返回的G0,操作的正常模式是从其它所有线程M中的其中一个线程Mn中“盗取”一个Pn。

            如果“盗取”不成功,它就会把它的G0放到一个全局运行队列中,然后把自己放到线程池中或者转入睡眠状态。

            这个全局运行队列是各个P在运行完自己的本地运行队列后用来获取新G的地方。

            各个P也会周期性的检查这个全局运行队列上的G,否则,全局运行队列上的G可能因得不到执行而被饿死。

            备注:Go程序要在多线程上运行的原因就是因为要处理系统调用,哪怕GOMAXPROCS等于1。运行时(runtime)使用调用系统调用的goroutines,而不是线程。

        “盗取”:

            当一个P运行完要被调度的所有G的时候。如果各个P的本地运行队列里的G的数目不均衡,改变就会发生了,

            否则会导致一个P1在执行完它的本地运行队列里的G后就会结束,尽管系统中仍然有许多G要执行。

            所以为了保持运行Go代码,一个P能够从全局运行队列中获取G,但是如果全局运行队列中也没有G了,那么P就不得不从其它Pn的运行队列获取G了。

            当一个P完成自己的任务后,它就会尝试“盗取”另一个P运行队列中G的一半。这将确保每个P总是有活干,然后反过来确保所有线程M尽可能处于最大负荷。

        备注:goroutine是按照抢占式调度的,一个goroutine最多执行10ms就会换作下一个。

            这个和目前主流系统的的cpu调度类似(按照时间分片)

            windows:20ms

            linux:5ms-800ms

golang goroutine的调度的更多相关文章

  1. Golang 协程调度

    一.线程模型 N:1模型,N个用户空间线程在1个内核空间线程上运行.优势是上下文切换非常快但是无法利用多核系统的优点. 1:1模型,1个内核空间线程运行一个用户空间线程.这种充分利用了多核系统的优势但 ...

  2. Goroutine并发调度模型深度解析之手撸一个协程池

    golanggoroutine协程池Groutine Pool高并发 并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环 ...

  3. 说说Golang goroutine并发那些事儿

    摘要:今天我们一起盘点一下Golang并发那些事儿. Golang.Golang.Golang 真的够浪,今天我们一起盘点一下Golang并发那些事儿,准确来说是goroutine,关于多线程并发,咱 ...

  4. goroutine与调度器

    29 November 2013 by skoo 我们都知道Go语言是原生支持语言级并发的,这个并发的最小逻辑单元就是goroutine.goroutine就是Go语言提供的一种用户态线程,当然这种用 ...

  5. Goroutine被动调度之一(18)

    本文是<Go语言调度器源代码情景分析>系列的第18篇,也是第四章<Goroutine被动调度>的第1小节. 前一章我们详细分析了调度器的调度策略,即调度器如何选取下一个进入运行 ...

  6. golang goroutine

    goroutine-介绍 1)进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位2)线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位.3 ...

  7. golang goroutine 介绍

    Goroutine 是用户态自己实现的线程,调度方式遇到IO/阻塞点方式就会让出cpu时间(其实也看编译器的实现,如果TA在代码里面插入一些yield,也是可以的. 反正现在不是抢占式的.) 不能设置 ...

  8. Golang进程权限调度包runtime三大函数Gosched、Goexit、GOMAXPROCS

    转自:https://www.cnblogs.com/wt645631686/p/9656046.html runtime.Gosched(),用于让出CPU时间片,让出当前goroutine的执行权 ...

  9. [Go] golang的MPG调度模型

    MPG模式运行状态11)当前程序有三个M,如果三个M都在一个cpu运行,就是并发,如果在不同的cpu运行就是并行2)M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有三个,M3的协 ...

随机推荐

  1. 存个emacs配置

    emacs配置 (global-set-key [f9] 'compile-file) (global-set-key [f10] 'gud-gdb) (global-set-key (kbd &qu ...

  2. Java语言的分支

    JavaSE:(标准版)是java基础,早期叫j2se,2005改名叫JavaSE(必须). JavaME:(移动版)适合移动端的开发.j2me,2005改名叫java ME(不学) JavaEE:( ...

  3. ansible基本使用教程

    转载请注明出处http://www.cnblogs.com/chenxianpao/p/7360349.html 一. 介绍 1. 简介     ansible是新出现的自动化运维工具,基于Pytho ...

  4. 《精通android网络开发》--HTTP数据通信

    No1: 例如:http://www.*****.com/china/index.htm 1)http:// 代表超文本传送协议,通知*****.com服务器显示web页,通常不用输入 2)www 代 ...

  5. POJ - 1062 昂贵的聘礼 Dijkstra

    思路:构造最短路模型,抽象出来一个源点,这个源点到第i个点的费用就是price[i],然后就能抽象出图来,终点是1. 任意两个人之间都有等级限制,就枚举所有最低等级限制,然后将不再区间[min_lev ...

  6. UVA-714 二分

    把可能的进行二分判断,判断的时候尽量向右取,一直取到不能去为止,这样才有可能成功分割. 判断是否可以把up作为最大值的代码: bool judge(LL up){ if(up < Big) re ...

  7. Vue中method与computed的区别

    为了说明method与computed的区别,在此我想先来看看computed属性在vue官网中的说法:模板内的表达式是非常便利的,但是它们实际上只用于简单的运算.在模板中放入太多的逻辑会让模板过重且 ...

  8. pc浏览器css和js计算浏览器宽度的差异以及和滚动条的关系

    如图: css宽度:1250 不包括滚动条宽度 用控制台箭头选取元素显示的左边的宽度:1250  不包含滚动条宽度 缩放浏览器右上角显示的宽度:1267 包含了滚动条宽度 再看下控制台: 由此可计算浏 ...

  9. JavaScript实现弹窗报错

    JavaScript实现弹窗报错 1.具体错误如下 SCRIPT 5022:cannot call methods on dialog prior to initialization; attempt ...

  10. Java获取某年某月的第一天

    Java获取某年某月的第一天 1.设计源码 FisrtDayOfMonth.java: /** * @Title:FisrtDayOfMonth.java * @Package:com.you.fre ...