go 调度器是部分抢占式的以及如何调度
1 | package main |
执行上述代码, print只在cpuIntensive执行完毕后才打印. 除非你在cpuIntensive中手动加入了runtime.Gosched(). 原因是密集的cpu计算过程并不包含可以抢占的抢占点(IO阻塞/垃圾回收等), 实际使用场景中很少会有这种, 但是如果你遇到这样的事情, 就需要手动的添加一些可以抢占的点, 手动除非调度器执行调度.
如果一个goroutine执行过程中到达了go中定义的可以抢占的点(IO/垃圾回收/等待输入等), 那么就会让出执行权给在同一个thread或全局goroutine队列中goroutine, 这个过程对于操作系统层面看是无感知的.切换的代价很小, 只需要变更3个寄存器的值,因此很轻量. go 多路复用thread线程, 这种实现不要求开发者自己去显示地通过事件循环和回调来处理futures/promises. 这种显示的处理需要是在同一个thread中手动创建事件循环,并利用操作系统提供的事件描述符与回调机制做处理.
go中block的goroutine会调用对应的操作系统内核提供的多路并发机制(epoll等)来等待执行结果的返回并同时让出当前线程的执行权限给其他goroutine.但是如果系统不支持IO多路复用,go会创建一个新的线程会去执行其他goroutine, 并等待到block结束后,重新返回这个goroutine,这时可能会存在多于cpu数量GOMAXPROCS的内核线程,这时就不受GOMAXPROCS控制了.
1 | evport -> soloris O(1) |
goroutine建立在事件驱动的架构之上. 同时并发的事件数量受到进程可以同时打开的文件描述符数量限制. 因此ulimit -n 必须设置一个合理值以供在进行大量同时的IO处理使用. 由于IO事件返回通常有一个超时设定, 返回后的文件描述符就会可被其他并发的事件使用, 因此一个较大的nofile值就足够cover大量的并发事件.
对于并发请求服务时,还需要考虑服务端是否有并发限制, 所以同时执行的goroutine数量还需要有一定的考虑