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. 【NOIP2015】运输计划

    [NOIP2015]运输计划 标签: 树上差分 LCA 二分答案 Description 公元 2044 年,人类进入了宇宙纪元. L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星 ...

  2. JavaScript 常用单词整理

    JS单词 push :添加一个数组元素 document :文档 pop :删除最后一个数组元素 console :控制台 shift :删除第一个数组元素 string :字符串 Concat 组合 ...

  3. python并发编程之多进程(一):进程开启方式&多进程

    一,进程的开启方式 利用模块开启进程 from multiprocessing import Process import time,random import os def piao(name): ...

  4. EF的Join()和Include()差异性教程

    在EF中表连接常用的有Join()和Include(),两者都可以实现两张表的连接,但又有所不同. 1.Join(),两表不必含有外键关系,需要代码手动指定连接外键相等(具有可拓展性,除了值相等,还能 ...

  5. 消息队列(MQ)入门-activemq,代码级别

    第一种:activemq: 1.从官网下载apache-activemq-5.15.3-bin.zip并解压: 2.启动activemq, CMD--/bin/activemq start ,访问12 ...

  6. shiro进行散列算法操作

    shiro最闪亮的四大特征:认证,权限,加密,会话管理 为了提高应用系统的安全性,这里主要关注shiro提供的密码服务模块: 1.加密工具类的熟悉 首先来个结构图,看看shiro提供了哪些加密工具类: ...

  7. PHP安全、Sql防注入安全汇总

    利用Mysqli和PDO 产生原因 主要就是一些数据没有经过严格的验证,然后直接拼接 SQL 去查询.导致漏洞产生,比如: $id = $_GET['id']; $sql = "SELECT ...

  8. C++学习,两个小的语法错误-network-programming

    1.bool CServerSocket::initSocket(const char* ip=NULL,const UINT &port)://会出现默认参数为2的错误 解决方案: //C+ ...

  9. pycharm安装,svn使用,远程开发调试,接口测试,连接服务器

    磨刀不误砍柴工,配置完美的编辑器,在开发时,能帮助我们节约大量的时间成本,从而是我们的精力放在业务逻辑实现上面! 接下来将介绍 使用pyhcarm如何使用svn,远程开发调试,接口测试,已经连接远程服 ...

  10. galera断电后无法重建集群

    节点有一个测试环境,数据库用的三节点galera,测试组的同事把电源同时断了.节后回来开机,发现数据库状态一直有问题,以前遇到这种情况,都是把一个节点中的my.conf中的wsrep配置全删掉,作为一 ...