转载

以coroutine-rs为例深入浅出Rust协程库

由于 Go 语言在国内的风靡,许多开发者开始了解和关注使用协程(Coroutine)来实现高并发程序,Rust语言在比较早期的开发版本中也在标准库提供了协程的支持,但在后来的讨论中去掉了。

8月13日晚,CSDN Rust 学习交流微信群(入群方式见文末)邀请了微信游戏开发组工程师、Rust 资深使用者 Elton 带了一节在线视频分享课: 【Rust 技术公开课】港哥 Elton 自主开发的协程库解析 ,深入浅出地带你重新认识协程库。

我们还精心制作了 Rust 开发技术学习路线 ,满满的都是干货。

分享人:

以coroutine-rs为例深入浅出Rust协程库

钟宇腾(Elton, GitHubLinkedIn ),圈内知名的Rust资深使用者,毕业于香港大学计算机系,现为微信游戏开发组工程师,负责微信游戏中心后台开发,游戏推荐系统开发。

热衷于Web开发、本地应用开发和计算机图形学、计算机视觉方面的研究,比较擅长于服务器端开发,特别是使用Python和Golang语言的服务器后台开发。

所做过的项目有: 基于Kinect的手势识别研究与实现使用Qt5编写的豆瓣FM播放器Go语言实现的XMPP聊天协议库使用C/C++实现一个可以在Linux/Unix下运行的H3C登录客户端sysuh3c使用Python实现一个交互式终端程序CMDClient-for-UPYUN

分享视频版(或点击 这里 ):

分享文字版:

首先介绍了一下Rust协程库coroutine-rs的开发目的:

  1. Rust最初是自带协程,基于libuv的非阻塞I/O支持;
  2. 后来经过社区讨论之后,Rust官方决定去掉协程支持,语言重新定位到保证并发安全上;
  3. shadowsocks-rust最初开发是基于Rust的协程库,但由于官方去掉了语言级别的支持,因此就自己做了一个协程库。

协程的简介

以coroutine-rs为例深入浅出Rust协程库

基于协程可以根据需要切换到其它协程的特点,它可以用来实现:

  • 协同任务(CooperativeTasks);
  • 事件调度(Event Loop);
  • 迭代器(Iterator);
  • 流(Stream, Infinite List);
  • 管道(Pipe);

协程并不是一个新的概念,现在已有不少的库在不同程度实现了协程基础及调度

  • l GNU Pth(PortableThreads) 这个库基本废弃;
  • l libtask 作者是Go语言的设计者之一RussCox;
  • l libcoro:非常实用的C语言协程库,在不少项目里都有使用;
  • l CO2:使用C++11实现的C++ Stackless协程库;
  • l Boost.Asio:基于Stackless协程的异步网络库;
  • l Boost.Coroutine:C++的Stackful协程库;
  • l Python Gevent:基于Greenlet;
  • l NetBSD Scheduler activation:M:N模型 一度内置在NetBSD系统中 后来讨论后去掉。

协程从实现方式上可以分成两种

以coroutine-rs为例深入浅出Rust协程库

协程的基本特点,可以由以下生产者-消费者的例子来说明

以coroutine-rs为例深入浅出Rust协程库

这段程序中,生产者produce首先向队列q中放入足够的item,然后使用yield to切换到消费者consume,消费者则从队列q中取出item并处理,然后使用yield to切换回生产者produce。在这个过程中,队列q并不需要加锁,因为在这个过程中并不会发生数据竞争,两个执行体produce和consume只会在yield to的时候才切换。

Stackful协程功能更强大,为什么还需要Stackless协程?

  • Stackless协程切换上下文所需的CPU cycle更少
  • Stackless协程没有独立的栈,内存占用少
  • 性能更好!

Coroutine-rs的实现细节

以coroutine-rs为例深入浅出Rust协程库

以coroutine-rs为例深入浅出Rust协程库

在这个例子中,介绍了库的基本用法。

Coroutine::spawn将创建一个协程,但不执行它。在resume方法调用时,流程就会跳到协程内,而Coroutine::sched方法调用时,流程就会从当前协程切换回去。

因此这段程序执行时会输出1 2 3 4 5。

以coroutine-rs为例深入浅出Rust协程库

在Thread Local放有一个Environment用于管理当前线程上运行的所有协程,它里面有一个栈,栈顶是当前正在运行的协程,当调用yield时,就从栈中pop出当前正在运行的协程,然后运行栈顶的协程;调用resume时,就把目标协程push到栈中,以维护协程之间的层级关系。

以coroutine-rs为例深入浅出Rust协程库

由于协程在不同的状态下可以进行的操作是不一样的,因此需要维护协程的状态,而如果是Clonable的协程,状态切换是保证线程安全的。

以coroutine-rs为例深入浅出Rust协程库

若把协程与异步I/O结合起来使用,那么就可以用同步的方法写代码,但是实际运行时却是异步的。

以coroutine-rs为例深入浅出Rust协程库

单线程调度时,协程在等待事件时,就会挂起协程并在事件循环中注册监听的事件,等到事件发生时,协程被事件循环唤醒,继续执行。

这种调度方式可以简单地把它变成多线程方式,但是每个线程之间并没有交流,各自处理自己的协程。在服务器框架中,可以采用一个专门的线程做Listener去接受客户端的请求,然后把任务分发到各个Worker线程中去,Worker中使用单线程的协程调度来实现。

另一种实现M:N模型的方法则是

以coroutine-rs为例深入浅出Rust协程库

在这种调度方式里面,所有的Worker线程都会从一个全局队列中取任务执行。这种简单的调度方式有个硬伤,就是所有的Worker线程都会去抢全局队列的锁,导致影响性能。

因此就有了一种新的方法:

以coroutine-rs为例深入浅出Rust协程库

每个Worker自己有一个私有的任务队列,优先会先把自己的任务处理完,再去全局任务队列中取一些任务过来。这样就可以减少在全局队列处的竞争,提高性能。

但这种方式又有一个硬伤,若一个Worker因为某些原因被阻塞住了,那么私有任务队列中的任务就饿死了。

以coroutine-rs为例深入浅出Rust协程库

这种调度方式中,若一个Worker做完了它自己的任务,全局任务队列里也没有任务了,那它会去别的Worker的队列中拿一些任务过来自己做。这种Work-stealing的策略目前是比较常用的一种,Go的Runtime就是使用这种方法。

而目前coroutine-rs库还存在一些问题:

以coroutine-rs为例深入浅出Rust协程库

由于coroutine-rs在做的时候是为了写一个网络库的,网络库的协程是不会在中间yield出去之后,就永远不回来了。在目前的Master分支上的版本,如果协程yield出去就不回来,那么在协程的栈上分配的变量就会泄漏了。

协程的一大特点,就是可以yield to到一个指定的协程。但是如果要实现这个功能,就需要使用unsafe,作为库的接口,提供unsafe会带来问题,因此目前还没有实现。

性能上因为使用了Thread Local,性能会有影响。而且因为考虑到线程安全,在切换的时候会加锁,也会影响性能。

后续工作

以coroutine-rs为例深入浅出Rust协程库

目前的应用

以coroutine-rs为例深入浅出Rust协程库

simplesched实现的就是前面所说的最简单的调度模型,里面实现的是一个调度精简的协程,切换时不会加锁。

而eventedco则是实现的更前面所说的单线程调度,mioco也是一样。

Rust目前依然把yield作为一个保留字,有一个RFC希望实现像C#的Stackless协程: https://github.com/rust-lang/rfcs/issues/388

编辑推荐本站 Rust 资源:

  • 【专家推荐】Rust开发技术学习路线(你想要的都在这里!) 
  • 【微信群分享】 王川:Rust与异步并发的那些事儿  
  • 【微信群分享】 唐刚:Rust是近15年最佳工程实践的集大成者   
  • 【技术文章】 为什么你需要近距离接触Rust 1.0
  • 【技术文章】 使用Nickel开发Web应用:从第一行代码到Heroku部署
  • 【专访】 专访Elton:浅谈C++、Go的挑战者Rust  
  • 【专访】 专访Liigo:我为什么要选择Rust?
  • 【Rust一周集锦】(一)、(二)

更多 Rust 信息和交流,请加入 CSDN Rust 学习交流群,大牛在线分享、讲课、视频等等,不容错过。 请加群主微信 qshuguang2008 或扫描下方二维码申请入群,需备注:实名+公司名+Rust。

以coroutine-rs为例深入浅出Rust协程库

极客头条 Rust 子社区 ,欢迎你来探讨和交流,直达地址: http://geek.csdn.net/forum/8

正文到此结束
Loading...