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. C#仪器数据文件解析-PDF文件

    不少仪器工作站输出的数据报告文件为PDF格式,PDF格式用于排版打印,但不易于数据解析,因此解析PDF数据需要首先读取到PDF文件中的文本内容,然后根据内容规则解析有意义的数据信息. C#解析PDF文 ...

  2. 轻松搭建Git服务器(Ubuntu)

    搭建Git服务器 在远程仓库节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改. GitHub就是一个免费托管开源代码的远程仓库.但是对于某些视源代码如生命的商业 ...

  3. CodeForces - 788B Weird journey 欧拉路

    题意:给定n个点,m条边,问能否找到多少条符合条件的路径.需要满足的条件:1.经过m-2条边两次,剩下两条边1次  2.任何两条路的终点和起点不能相同. 欧拉路的条件:存在两个或者0个奇度顶点. 思路 ...

  4. UVA - 658 最短路

    思路:通过前后两种状态建立一条边,利用Dijsktra就可以做了. 注意利用二进制优化. AC代码 #include <cstdio> #include <cmath> #in ...

  5. 打造SpringBootTemplate(SpringBoot项目的模版)

    随着框架使用的不断的更新,后面使用SpringBoot会多,这边准备构建一个SpringBoot项目使用的模版. 所谓模版,和之前一样,就是一个最简单的项目,包含所有最简单的空实现. 做模版的时候参考 ...

  6. filebeat -> logstash -> elasticsearch -> kibana ELK 日志收集搭建

    Filebeat 安装参考 http://blog.csdn.net/kk185800961/article/details/54579376 elasticsearch 安装参考http://blo ...

  7. ThinkPad W500 清灰记录

    转载请注明出处:HateMath归来(http://www.cnblogs.com/hatemath/) 看型号就知道,这是一台英雄迟暮型的老电脑.到了夏天,启动后啥事不做,通风口都烫手.心情好,一时 ...

  8. Luogu P1522 牛的旅行 Cow Tours

    题目描述 农民 John的农场里有很多牧区.有的路径连接一些特定的牧区.一片所有连通的牧区称为一个牧场.但是就目前而言,你能看到至少有两个牧区通过任何路径都不连通.这样,Farmer John就有多个 ...

  9. 【DDD】领域驱动设计实践 —— 一些问题及想法

    在社区系统的DDD实践过程中,将遇到一些问题和产生的想法记录下来,共讨论. 本文为[DDD]系列文章中的其中一篇,其他内容可参考:使用领域驱动设计思想实现业务系统. 1.dto.model和entit ...

  10. 嵌入式Linux引导过程之1.6——Xloader的Xloader_Entry

    我们已经看完了XLOADER_ENTRY里调用的前两个标号的代码,分别是sys_init和ddr_init.对于一个嵌入式系统来说,这两 个部分的代码是在一开始就执行的,至少是在从bootrom里面的 ...