视频地址
视频说明
这个视频是说Golang的Goroutine的设计和调度,说得挺好
讲解的过程
视频里面循序渐进地讲述了Goroutine的Scheduler是如何设计的。
为什么需要协程
1.协程比系统线程要轻量级:不管是从创建销毁还是所占资源上面来说。
2.协程的调度比线程调度要快。
3.并发
Goroutine 和 系统线程如何协同工作
1.如何把Goroutine在线程之间分配。
2.在什么时候创建新的线程。
核心数据结构
核心数据结构 – runqueues
这个是一个分配在堆上面的队列,里面装的是待调度的Goroutine。
循序渐进的设计
第一版
把所有的Goroutine放在一个系统线程里面运行
问题:
1.如果一个Goroutine被阻塞,那么后面的Goroutine将不会得到运行的机会。
2.不能利用多核。
第二版
为每一个Goroutine创建一个线程。
问题:
这个跟直接创建一个线程再执行任务没什么区别。
第三版
复用线程:每当线程的数量小于Goroutine的数量时,创建一个新的线程,使用这个线程去运行Goroutine,但是运行完Goroutine之后,并不会把这个线程销毁,而是放入线程池。
问题:
线程会很多,并且对核心数据结构的加锁会影响效率,也会影响可扩展性。
线程会达到一个上限,不可能无止境地创建线程。
第四版
线程池:创建指定数量的线程,这个指定的数量为CPU的核数,这些线程都会操作同一个runqueue
问题
当CPU核心数多了之后,还是会出现锁的低效问题
第五版
为每一个线程分配一个runqueue,在哪一个线程里面创建Goroutine,那么就把Goroutine放入那个线程的runqueue
问题:
1.如何去平衡线程之间的Goroutine数量?
work stealing,如果当前线程的runqueue是空的,那么就去其他线程的runqueue里面去要几个过来。
2.当一个线程阻塞了,他创建的Goroutine怎么办?
如果有其他的线程处于空闲状态,那么其他线程会把这个Goroutine拿过去运行。如果其他线程都阻塞了,那么会创建一个新的线程来执行这个Goroutine,这个新的线程还是有一个runqueue
注意点
1.GOMAXPROCS:运行Goroutine的最大线程数量,所以如果很多的Goroutine阻塞[新任务会阻塞],那么还是会创建很多的线程出来。
2.会有一个monitor在后面监视这些线程的运行情况。
3.如果一个Goroutine占用了过多的CPU资源怎么办:在后台运行一个sysmon的线程,这个线程会去检测运行过久的Goroutine,并且把运行过久[>10ms]的Goroutine暂停掉,并且把暂停掉的Goroutine放到一个全局的runqueue里面。
当前调度器的缺陷
1.runqueue只是一个没有优先级的队列,所以会按照先进先出的顺序来运行Goroutine。
2.调度Goroutine时公平性没有很好的保证:已经提议进行修改。
3.runqueue没有利用缓存,使用缓存[栈而不是队列]可以加速Goroutine的访问。