注意,原文发布日期为2017年5月3日,至今已有3年 , 而 go 语言已然从 1.10 进化到了 如今的 1.14 所以本篇的内容时效性并不做保证,仅用以学习理解。
事实上,操作系统运行线程,也就是你的代码所运行的地方。 Go 做的 “把戏” 就是,使用编译器把不同系统的系统调用注入 go 的 runtime 内, 所以 Go 可以响应调度器并且执行动作。
我们可以看到, 这里有两种给 G 用的队列: 一个是实现于 schedt struct ( 极少会被用到的 )的全局队列,以及每一个 P 所维护的一整套 可运行的 G 的队列 。
当你调度了一个新的 goroutine (执行一个go func()),意味着你把它压入了 P 的队列,他们有一个很有趣的 偷工作 调度算法,当 M 完成执行了一些 G 后 ,它就会设法从队列中取出其他 G 当其中一个 P 的队列空了之后 , G 会从其他 P 中尝试 “偷” 掉一半的可执行的 G !
当你的 goroutine 做了一个阻塞性的系统调用(syscall)时,有趣的事情就发生了。 阻塞性的系统调用会被拦截, 如果当时有其他的 G 要执行时,runtime 会把这个线程从 P 中分离 ,并创建一个新的 OS 线程 ( 当闲置线程不存在时 ) ,来服务这个处理器。
当系统调用回复时,此 goroutine 会放回本地可执行队列,并且线程会自动释放(*此处用了park ,即停车,应该可以理解为释放为闲置资源。)(意味着线程不再使用) , 并把自己写入闲置线程列表。
如果一个 goroutine 执行了一个网络调用,runtime 会做一相似的动作。这个调用会被拦截,但因为 Go 有一套拥有自己线程的网络池 , 它们会被分配到这些任务。
本质上,当目前的gooutine 被阻塞在:
阻塞的系统调用(比如打开一个文件),
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!