goroutine(1) go的调度器

对多道并行执行的程序来讲,有时它要占用处理器运行,有时要等待传送信息,当获得信息后又可继续执行,一个程序的执行可能受到另外一个程序的约束。因此程序的执行其实是走走停停的,为了能正确反映程序执行时的活动规律和状态变化,引进了进程,以便从变化的角度,动态地分析和研究程序的执行。数据库

用计算机系统来解决某个问题时首先必须编制程序,可把程序看做是具备独立功能的一组指令的集合,或者说是指出处理器执行操做的步骤。程序的执行必须依赖于一个实体-数据集缓存

还有一个问题就是从程序的角度没法正确描述程序执行时的状态。例:只有一个“编译程序”,同时为多个用户服务,有两个须要编译的程序甲、乙,假定编译程序p从a点开始工做,如今正在编译程序甲,当工做到b点时,须要把中间结果记录在磁盘上。因而程序p在b 点等待磁盘传输信息。这时处理器空闲,这时让p为源程序 乙进行编译,编译程序仍从a点开始工做。如今 的问题是如何来描述p的状态?是说“它在b点等侍磁盘传输”仍是说“从a点开始执行?多线程

1.进程和线程并发

进程:把一个程序在一个数据集上的一次执行称为一个进程spa

  (程序是静止的,进程是动态的。在20世纪80年代,大多数操做系统仍采用进程技术,把进程做为操做系统的基本组成单位。进程既是资源分配单位,又是调度和执行单位)操作系统

线程:线程是进程中可独立执行的子任务。线程

  (把一个计算问题或一个应用问题做为一个进程,把该进程中能够并发执行的各部分分别做为线程。一个数据库应用程序的运行建立了一个数据库进程,用户要求从数据库中生产一份工资报表,并将它传送到一个文件中。用户在等待报表生成的时候又向数据库提出一个查询请求。操做系统把用户的每个请求(工资单报表,数据库查询)分别做为数据库进程的一个线程)协程

为何引入线程?blog

  1.每一个进程都要占用一个进程控制块和一个私有的主存空间,开销比较大;进程之间传递消息时要从一个工做区传到另外一个工做区,需专用通讯机制,速度较慢队列

  2.进程增多就增长了进程调度的次数,给调度和控制带来了复杂性。

  在采用线程的操做系统中,进程是资源分配 单位,而线程是调度、执行单位。

协程:独⽴的栈空间,共享堆空间,调度由⽤户⾃⼰控制,本质上有点相似于⽤户级线程,这些⽤户级线程的调度也是⾃⼰实现的 。⼀个线程上能够跑多个协程,协程是轻量级的线程。

进程——>一个线程——>单线程程序

进程——>多线程——>多线程程序

2.并发和并行

1)多线程程序在cpu一个核蕊上运行就是并发

2)多线程程序在cpu多个核蕊上运行就是并行

并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。

并发:

 

并行:

 3.用户空间线程和内核空间线程之间的映射关系 

N:1,   1:1和   M:N

N:1是说,多个(N)用户线程始终在一个内核线程上跑,context上下文切换确实很快,可是没法真正的利用多核。

1:1是说,一个用户线程就只在一个内核线程上跑,这时能够利用多核,可是上下文switch很慢。

M:N是说, 多个goroutine在多个内核线程上跑,这个看似能够集齐上面二者的优点,可是无疑增长了调度的难度。

4.go的调度器

go 的高度器的内部结构:M P G

 

 

M:表明真正的内核OS线程,和POSIX里的thread差很少,真正干活的人
G:表明一个goroutine,它有本身的栈,instruction pointer和其余信息(正在等待的channel等等),用于调度。
P:表明调度的上下文,能够把它看作一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。
 

图中看,有2个物理线程M,每个M都拥有一个context(P),每个也都有一个正在运行的goroutine。

P的数量能够经过GOMAXPROCS()来设置,它其实也就表明了真正的并发度,即有多少个goroutine能够同时运行。

图中灰色的那些goroutine并无运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue),

Go语言里,启动一个goroutine很容易:go function 就行,因此每有一个go语句被执行,runqueue队列就在其末尾加入一个
goroutine,在下一个调度点,就从runqueue中取出(如何决定取哪一个goroutine?)一个goroutine执行。
 
为什么要维护多个上下文P?由于当一个OS线程被阻塞时,P能够转而投奔另外一个OS线程!
图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行因此的context P。
图中的M1多是被建立,或者从线程缓存中取出。

 

当MO返回时,它必须尝试取得一个context P来运行goroutine,通常状况下,它会从其余的OS线程那里steal偷一个context过来,
若是没有偷到的话,它就把goroutine放在一个global runqueue里,而后本身就去睡大觉了(放入线程缓存里)。Contexts们也会周期性的检查global runqueue,不然global runqueue上的goroutine永远没法执行。

 

另外一种状况是P所分配的任务G很快就执行完了(分配不均),这就致使了一个上下文P闲着没事儿干而系统却任然忙碌。可是若是global runqueue没有任务G了,那么P就不得不从其余的上下文P那里拿一些G来执行。通常来讲,若是上下文P从其余的上下文P那里要偷一个任务的话,通常就‘偷’run queue的一半,这就确保了每一个OS线程都能充分的使用。
参考:https://www.zhihu.com/question/20862617
相关文章
相关标签/搜索