GO协程
goroutine
协程本意是coroutine,go协程是goroutine。
线程分为内核态线程(内核线程)和用户态线程(协程)。
线程由CPU调度是抢占式的,协程由用户态调度是协作式的(主动让出才执行下一个)。
N:1(N个用户线程绑定1个内核线程)
例子:python的gevent
优点:协程切换不陷入内核态,轻量快速。
缺点:无法充分利用CPU,协程阻塞后造成线程阻塞,其他协程都无法执行。
1:1(1个用户线程绑定1个内核线程)
例子:java.lang.Thread、C++11的std::thread
优点:充分利用CPU。
缺点:不同内核态线程之间上下文切换很慢。
M:N(M个用户线程绑定N个内核线程)
克服了以上2种映射关系的缺点。
go协程,goroutine没有父子关系,main函数是主goroutine。
不同于coroutine的2个地方
1. 如果协程阻塞造成线程阻塞,那么该线程的其他协程可以转移到其他可运行的线程上。
2. 一个goroutine最多占用CPU 10ms,防止其他goroutine饿死。
go函数执行时机
输出结果
Hello, Mark
Hello, Mark
Hello, Mark
Hello, Mark
Hello, Mark
解释
在for语句执行完毕之后才执行5个go函数,这时name的值是Mark。
改进1
改进2
GO调度器的组成
G、M和P的概念
G是goroutine。
M是内核线程。
P是处理器,最多GOMAXPROCS(默认值是CPU核数,最大化并行)个。
M必须和一个P关联才能运行P拥有的G。
自顶向下GO调度器的4个部分
全局队列:存放等待运行的G。
P的本地队列:存放等待运行的G。新建G时,G优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。
M:关联P后,从P的本地队列获取G并运行。1. 如果P队列为空时,那么从其他P的本地队列偷一半放到P的本地队列。如果偷取失败,那么从全局队列拿G放到P的本地队列。2. 因G执行系统调用造成线程阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行。
GO调度器和OS调度器通过M结合起来,GO调度器绑定G和M,OS调度器绑定M和CPU。
参考资料