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. MySQL数据库基础(四)(子查询与链接)

    1.子查询简介 其中,所谓的"外层查询"并不是指"查找",指的是所有SQL语句的统称:结构化查询语言(Structured Query Language),简称 ...

  2. python并发编程之多进程(三):共享数据&进程池

    一,共享数据 展望未来,基于消息传递的并发编程是大势所趋 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合 通过消息队列交换数据.这样极大地减少了对使用锁定和其他同步手段的需求, 还可以扩展 ...

  3. python2 => python3 踩坑集合

    报错内容: ModuleNotFoundError: No module named 'md5' 解析: 这是 python2 的库,python3 已经把它包含进 hashlib 库里了 解决方法 ...

  4. eclipse中创建一个maven项目

    1.什么是Maven Apache Maven 是一个项目管理和整合工具.基于工程对象模型(POM)的概念,通过一个中央信息管理模块,Maven 能够管理项目的构建.报告和文档. Maven工程结构和 ...

  5. 让网站通过Https访问

    Prerequisites Before you begin, you should have some configuration already taken care of. We will be ...

  6. 傅里叶变换 - Fourier Transform

    傅里叶级数 傅里叶在他的专著<热的解析理论>中提出,任何一个周期函数都可以表示为若干个正弦函数的和,即: \[f(t)=a_0+\sum_{n=1}^{\infty}(a_ncos(n\o ...

  7. JavaScript实现排序二叉树的相关算法

    1.创建排序二叉树的构造函数 /** * 创建排序二叉树的构造函数 * @param valArr 排序二叉树中节点的值 * @constructor */ function BinaryTree(v ...

  8. 【视频教程】一步步将AppBox升级到Pro版

    本系列教程分为上中下三部分,通过视频的形式讲解如何将基于FineUI(开源版)的AppBox v6.0一步一步升级FineUIPro(基础版). [视频教程]一步步将AppBox升级到Pro版(上)主 ...

  9. Mysql group by,order by,dinstict优化

    1.order by优化 2.group by优化 3.Dinstinct 优化 1.order by优化 实现方式: 1. 根据索引字段排序,利用索引取出的数据已经是排好序的,直接返回给客户端: 2 ...

  10. Object方法

    1. getClass() 返回此 Object 的运行时类. 2. hashCode() 返回该对象的哈希码值. 3. equals() 指示其他某个对象是否与此对象“相等”. 4. toStrin ...