由于 Go 语言在国内的风靡,许多开发者开始了解和关注使用协程(Coroutine)来实现高并发程序,Rust语言在比较早期的开发版本中也在标准库提供了协程的支持,但在后来的讨论中去掉了。
8月13日晚,CSDN Rust 学习交流微信群(入群方式见文末)邀请了微信游戏开发组工程师、Rust 资深使用者 Elton 带了一节在线视频分享课: 【Rust 技术公开课】港哥 Elton 自主开发的协程库解析 ,深入浅出地带你重新认识协程库。
分享人:
钟宇腾(Elton, GitHub 、 LinkedIn ),圈内知名的Rust资深使用者,毕业于香港大学计算机系,现为微信游戏开发组工程师,负责微信游戏中心后台开发,游戏推荐系统开发。
热衷于Web开发、本地应用开发和计算机图形学、计算机视觉方面的研究,比较擅长于服务器端开发,特别是使用Python和Golang语言的服务器后台开发。
所做过的项目有: 基于Kinect的手势识别研究与实现 、 使用Qt5编写的豆瓣FM播放器 、 Go语言实现的XMPP聊天协议库 、 使用C/C++实现一个可以在Linux/Unix下运行的H3C登录客户端sysuh3c 、 使用Python实现一个交互式终端程序CMDClient-for-UPYUN 。
分享视频版(或点击 这里 ):
分享文字版:
首先介绍了一下Rust协程库coroutine-rs的开发目的:
协程的简介
基于协程可以根据需要切换到其它协程的特点,它可以用来实现:
协程并不是一个新的概念,现在已有不少的库在不同程度实现了协程基础及调度
协程从实现方式上可以分成两种
协程的基本特点,可以由以下生产者-消费者的例子来说明
这段程序中,生产者produce首先向队列q中放入足够的item,然后使用yield to切换到消费者consume,消费者则从队列q中取出item并处理,然后使用yield to切换回生产者produce。在这个过程中,队列q并不需要加锁,因为在这个过程中并不会发生数据竞争,两个执行体produce和consume只会在yield to的时候才切换。
Stackful协程功能更强大,为什么还需要Stackless协程?
Coroutine-rs的实现细节
在这个例子中,介绍了库的基本用法。
Coroutine::spawn将创建一个协程,但不执行它。在resume方法调用时,流程就会跳到协程内,而Coroutine::sched方法调用时,流程就会从当前协程切换回去。
因此这段程序执行时会输出1 2 3 4 5。
在Thread Local放有一个Environment用于管理当前线程上运行的所有协程,它里面有一个栈,栈顶是当前正在运行的协程,当调用yield时,就从栈中pop出当前正在运行的协程,然后运行栈顶的协程;调用resume时,就把目标协程push到栈中,以维护协程之间的层级关系。
由于协程在不同的状态下可以进行的操作是不一样的,因此需要维护协程的状态,而如果是Clonable的协程,状态切换是保证线程安全的。
若把协程与异步I/O结合起来使用,那么就可以用同步的方法写代码,但是实际运行时却是异步的。
单线程调度时,协程在等待事件时,就会挂起协程并在事件循环中注册监听的事件,等到事件发生时,协程被事件循环唤醒,继续执行。
这种调度方式可以简单地把它变成多线程方式,但是每个线程之间并没有交流,各自处理自己的协程。在服务器框架中,可以采用一个专门的线程做Listener去接受客户端的请求,然后把任务分发到各个Worker线程中去,Worker中使用单线程的协程调度来实现。
另一种实现M:N模型的方法则是
在这种调度方式里面,所有的Worker线程都会从一个全局队列中取任务执行。这种简单的调度方式有个硬伤,就是所有的Worker线程都会去抢全局队列的锁,导致影响性能。
因此就有了一种新的方法:
每个Worker自己有一个私有的任务队列,优先会先把自己的任务处理完,再去全局任务队列中取一些任务过来。这样就可以减少在全局队列处的竞争,提高性能。
但这种方式又有一个硬伤,若一个Worker因为某些原因被阻塞住了,那么私有任务队列中的任务就饿死了。
这种调度方式中,若一个Worker做完了它自己的任务,全局任务队列里也没有任务了,那它会去别的Worker的队列中拿一些任务过来自己做。这种Work-stealing的策略目前是比较常用的一种,Go的Runtime就是使用这种方法。
而目前coroutine-rs库还存在一些问题:
由于coroutine-rs在做的时候是为了写一个网络库的,网络库的协程是不会在中间yield出去之后,就永远不回来了。在目前的Master分支上的版本,如果协程yield出去就不回来,那么在协程的栈上分配的变量就会泄漏了。
协程的一大特点,就是可以yield to到一个指定的协程。但是如果要实现这个功能,就需要使用unsafe,作为库的接口,提供unsafe会带来问题,因此目前还没有实现。
性能上因为使用了Thread Local,性能会有影响。而且因为考虑到线程安全,在切换的时候会加锁,也会影响性能。
后续工作
目前的应用
simplesched实现的就是前面所说的最简单的调度模型,里面实现的是一个调度精简的协程,切换时不会加锁。
而eventedco则是实现的更前面所说的单线程调度,mioco也是一样。
Rust目前依然把yield作为一个保留字,有一个RFC希望实现像C#的Stackless协程: https://github.com/rust-lang/rfcs/issues/388
编辑推荐本站 Rust 资源:
更多 Rust 信息和交流,请加入 CSDN Rust 学习交流群,大牛在线分享、讲课、视频等等,不容错过。 请加群主微信 qshuguang2008 或扫描下方二维码申请入群,需备注:实名+公司名+Rust。
极客头条 Rust 子社区 ,欢迎你来探讨和交流,直达地址: http://geek.csdn.net/forum/8