Tokio scheduler,Rust Future,Go Routine
刚看了一篇文章,讲Tokio的调度器实现,如何优化比上一版的效率提升了10倍之多。主要是讲了work-stealing的实现,如何实现高效deque。作者参考了很多文章,包括从Go的调度器实现代码里吸取了很多精华。所以,阅读这篇收获很大,基本了解了主流的任务调度实现原理。
Tokio主要是基于Rust的Futures实现的,而Rust的的futures号称是zero cost abstraction,这两天了解一下果然是很高效。
罗列一下
- 只有在poll的时候才做功,从而避免了无用功,以及canceling,还有backpressure。
- 使用waker来唤醒,避免无谓poll。
- 使用intrusive容器,避免内存分配。instrusive在c里很常见,但是在c++里因为std是non-instrusive的,所以都被人遗忘了。boost里有instrusive的实现。
- 每个thread有自己的队列,这样就不用加锁了。还有,还有全局队列,用于装溢出的任务。。。
因为大量借鉴了Go的实现,所以不禁好奇Go的Go Routine是如何实现的?
相比Future,Go Routine还是更简单的。Future的and_then, map之类的总觉得很累赘。通过使用async/await,会变得更自然一点。
Go Routine就像正常代码一样。
Rust的Future使用了State Machine来保存状态,Rust的await实现,底层是由generator/coroutine实现的,由编译器生成代码,通过状态机来实现返回。
Go Routine使用heap来模拟Stack(每个goroutine初始有2K的stack,虽然轻量也不是完全没有代价)。
Rust因为没有Runtime,所以把很多工作交给了开发者来做,比如Async里要释放控制权,需要调用.wait(),而Go里是自动的(在遇到系统调用,io阻塞操作的时候)。因为Go是有runtime的,跟net包也是紧密配合的。
因为遇到的问题类似,很多解决问题的办法也是相通的,了解一下就可以触类旁通了。
Making the Tokio scheduler 10x faster 介绍tokio调度器的工作原理
Performance without the event loop Go的调度原理
What Are Tokio and Async IO All About? Go Routine和Rust Async的比较,可以看到Rust还在不断演变
How Stacks are Handled in Go Go栈管理的演变
How Goroutines Work 与Thread相比,Goroutine的初始栈空间很小,因为不是抢占式任务切换,所以不需要保存很多CPU的寄存器。对于长时间运行的循环,还可以在调用函数时进行Goroutine的切换,避免一个Goroutine长期占据一个Thread~
The Go scheduler Go的调度原理,对GOMAXPROCS的解释比前面的清楚很多,讲了Context的切换,如何在系统调用进入之前放弃Context。
原文时间:2019.11