作者回复: 好问题。 1. 传统理解的coroutine一般是面向协作式,而非抢占式。像python中通过yield关键字创建的协程,与主routine之间是在一个线程上实现的切换执行,从设计角度是通过coroutine实现了并发(concurrency),但其实它们还是串行执行的,不会真正并行(paralellism),即便在多核处理器上。 基于上面的理解,我们就可以意识到goroutine并非传统意义上的coroutine,是支持抢占的,而且也必须依赖抢占实现runtime对goroutine的调度。它更像thread,可以绑定不同的cpu核并行执行(如果是在多核处理器上的话)。同时基于goroutine的设计也会一种并发的设计。 而goroutine与thread又不同,goroutine是在用户层(相较于os的内核层)调度的,os并不知道其存在,goroutine的切换相对轻量。而thread是os 来调度的,切换代价更高一些。 所以文中将goroutine称为“轻量级线程”,而不是协程。 2. 你理解的没错。这节课是为了演示goroutine、channel之间的调度与通信机制而“设计”出来的。goroutine使用代价很低,通常不用考虑池化。但是在一些大型网络服务程序时,一旦goroutine数量过多,内存占用以及调度goroutine的代价就不能不考虑了。于是有了“池化”的思路。这与传统的线程池的思路的确是一脉相承的 3. go是gc的,内存不会越来越大。
作者回复: 原文中有源码的链接,在最后。 源码在 https://github.com/bigwhite/publication/tree/master/column/timegeek/go-first-course/35 看了后,就可以回答你的问题了。
作者回复: 看的真细致!👍 1. worker一旦创建后,除了panic和quit通知退出,worker是不会退出的,也就是没有所谓“正常退出”的情况。所以没在defer中调用<-p.active。 2. 的确是笔误,感谢指出。 3. 在文后有源码链接。这里的task仅是触发了worker创建,这里是调度循环,不处理task,所以要把task扔回tasks channel,等worker启动后再处理。
作者回复: 当preAlloc=false时,即不预分配时。这样就根据tasks的情况来创建worker。如果当前没有task,实际上系统中没有worker被创建出来,直到有task才会创建worker。一直到active channel满了!这样pool中所有worker都创建出来后,再跳出循环,进入下面的quit监听。 不过workpool2的这段代码的确有refactor的空间😁。可以写的更好理解一些。
作者回复: demo1创建goroutine后没有回收,一直是复用的,最多创建capacity个goroutine,直到pool销毁。
作者回复: run在New中调用,active满了,代表capacity个worker goroutine已经创建完毕,后续将重用这些goroutine。这时候run会阻塞在select上直到quit channel有数据才会退出。p.active 那个case后续没用了。
作者回复: 当preAlloc=false时,即不预分配时,有用。
作者回复: “关键变化”,指的是?
作者回复: 最后一版Schedule加入了default分支,当pool资源不够又设置为non block时,schedule肯定会返回error啊。
作者回复: 问题1:主要是为了可选参数吧。为什么go没有原生支持默认参数与可选参数,我猜是因为go设计者压根就不想引入这个复杂性。 问题2: 和goroutine的调度顺序有关。