日前,CSDN 采访了 Rust 方面资深使用者庄晓立和Elton,很多人对于Rust在应用方面有着极大的兴趣,为此我们建立了 CSDN Rust 学习交流群(见文末的二维码),定期邀请业内资深大牛来群里分享他们的故事和心得。
我们还精心制作了 Rust 开发技术学习路线 ,满满的都是干货。
与此同时,我们也还邀请了圈内知名的Rust资深使用者Elton,他毕业于香港大学计算机系毕业,现为微信游戏开发组工程师,负责微信游戏中心后台开发,游戏推荐系统开发。Elton将带你全面认识他自主开发的协程库,Rust进阶者不容错过哦。赶紧点击下面的链接报名吧!
【Rust技术公开课】港哥Elton自主开发的协程库解析
本期微信群的分享情况如下:
王川 ( GitHub 、 LinkedIn ),水泊梁山人,玫瑰星云创始人,大学之前写各种 BASIC。2007 年毕业于青岛大学计算机专业,随后来到北京工作,从事 Python 开发 6 年多,先后任职于 Exoweb、Happylatte,高级软件工程师。2013 年投入手游创业中,作为 CTO 带领团队完成了基于 Python 的产品后端,及基于 Unity 3D 的产品前端。期间,不断地研究高并发可伸缩的服务器设计,应用于 Web、云计算和游戏后端等。另外,他也热衷于开源事业,北京 Linux 用户组成员,活跃于各种社区和线下聚会上,参与或创立了许多开源项目。
CSDN:请和大家介绍下你和目前所从事的工作。
王川: 我是一个 Python 程序员(呵呵),从小折腾小霸王 BASIC 的那种,通过 Java 和 FreeBSD 接触到开源社区,后来主要在搞一些 Python 和 ArchLinux 的项目。工作上前 5 年在打工,最近 3 年在瞎折腾,做过手游、各种外包、矿池理财什么的,但很明显还没有折腾出个所以然。
目前在跟几个合伙人创业做一个叫“ 优定 ”的产品,一个豪车线下社交的App,豪车车主和高颜值女性可以在优定App上有偿匹配到对方,优定App会引导完成一次完美的线下社交之旅。
优定将于近日登陆 iOS 平台,敬请关注!我们也在寻找投资机会,您若有意向请在完成 20 次优定匹配后,在应用内拨打 404 洽询。
CSDN:你是一位连续的技术人员创业者,能否谈下历次的创业项目,以及有什么心得和体会。
王川: 连续谈不上,其实也就两三次,13 年做手游没赶上最好的时候,14 年过渡期算是帮朋友做过一些七七八八的项目,今年就是做优定。感受最深的就是合伙人的重要性,当然这在一定程度上听上去与“致富的道路上有个富爹非常重要”类似的然并卵,但当能力和资源有短板时能碰到几个三观相似目标相同能力互补的合伙人也是非常幸运的。除此,共同拥有一个坚定的目标也是至关重要的,一切手段皆要奔着这个目的去。
CSDN:业内有人说「技术人员并不适合创业」,对此你怎么看?技术人员创业要具备哪些品质?
王川: 也许这个概念是说,技术人员并不适合在创业中以技术思维去解决自己并不擅长的事情,不过我觉得这两个条件:A 技术思维 B 不擅长 是 and 的关系——当然这个想法也是有技术思维风险的——单独不擅长没关系,技术思维有时也好事,比如穷尽各种 corner case 规避风险、对复杂问题的概括抽象等,但在不擅长的事情上照搬技术思维就可能会出 bug。比如说产品临上线,忽然看到一处体验缺陷,虽然只影响少数用户,但碰到问题的用户多半会流失;这时技术思维给出的答案多半是延期发布,连夜修问题,因为技术上是可以修的,而且不修会亏钱嘛;不说上线延期对市场、运营或团队的影响,怎么保证修一个问题不会弄坏东西或带来更多其他问题,而一再延期?所以这时跳出技术思维,从产品的角度考虑或与更擅长的合伙人讨论,能不能找到 work around,绕过问题不让用户碰到,或者碰到但不会流失,这样的方案再与从技术角度彻底解决来做对比,哪个方案更有利于实现最终目标,就做哪个。我觉得这也许可以总结为“做个吃货,做个二货”(摘自知乎 Stay hungry. Stay foolish)?
CSDN:你是如何接触到Rust的?
王川: 2014 年初,课余时间折腾 Gevent for Python 3 的时候,通过 libev、libuv 看到了当时还在用 libuv 的 Rust 语言,正巧自己也琢磨着学一门没有 vm 的“硬货”语言,又看到了 赖总的评价 ,大致 了解了一些 之后,又草草对比了一下 Go,感觉价值观契合度爆表,于是就加了关注。
CSDN:你认为Rust是一种怎样的编程语言?
王川: 从不是说吧。我认为
综合来看,我觉得 Rust 是一门还挺值得试一下的、有前途的、优秀的编程语言。
CSDN:你在哪些工程项目中使用到了Rust?
王川: 工程项目没有(囧),学术项目 zmq.rs 算是半个吧,因为这个做到一半 Rust 本身碰到了大改动,项目本身也需要返工,加上后来自己时间不够,就先搁在那里了。
CSDN:对于Rust新人的学习,你有哪些建议?
王川: 以兴趣为导向,实践出真知!比如我感兴趣高并发这一块儿,于是进了门,然后一遍学语法一遍折腾着写 zmq.rs ,还是非常有效的。手头上没有现成想做的东西的同学可以跟 Rust by Examples,也是很不错的。另外泛泛的建议是,多多提高 Get Things Done 的能力,be geeky,做个吃货,做个二货。
zmq.rs 是用 Rust 重新实现的 ZeroMQ 库。ZeroMQ 是一款非主流的消息队列库,但其实是一个网络并发框架,简单来说就是用同样的接口、通过不同的媒介(TCP、进程间、进程内)、套用不同的模式(请求响应、发布订阅、任务分发、扇出)在多个 socket 之间传递原子信息数据。具体来说就是这样:
图是去年 WOT 会上的演讲里扒出来的,大概说明一下 ZeroMQ 是什么:ZeroMQ 的 socket 本身没有什么区别,都是一个收接口 recv 一个发接口 send,只是应用不同的收发消息模式让他们有了不同的类型,比如 REQ、REP、PUB、SUB 等。每个 ZeroMQ socket 都可以监听多个 endpoint——可以是一个 TCP 端口、也可以是一个 UNIX domain sokcet 等,同时还可以去连接多个别的 ZeroMQ socket 监听的 endpoint;只要双方的模式配套,就可以实现正常的消息收发。比如对于图中的 REP 来说,recv 会公平地从不同客户端收取消息,而接着调用的 send 则会把相应返回给对应的客户端。
像 ZeroMQ 这种重度的网络 I/O 集中、连接数又多的库,是适合使用异步并发模型来实现的,官方的 ZeroMQ 实现正是如此,但是用 C++ 实现的,叫做 libzmq。简单来说(异步基础知识部分,了解的同学可小憩片刻),libzmq 会开启几个 worker 线程,每个 worker 里跑着一个循环,叫做事件循环(EventLoop),每次循环会处理一个排队进来的异步任务,比如发一条消息,或者处理一个超时的对象等。对于 libzmq 的用户来说,你从自己的线程里调用 send,只是把要发送的数据放在了一个队列里,只有当某个 worker 的 EventLoop 处理到这条任务时,数据才会真正被发送,这叫做异步。并发则是说,用 libzmq 你可以同时连接很多个不同的 socket,每一个都在不停地异步收发消息——你的处理可能是单线程的,但在 worker 里却是并发的(假如只有一个 worker,用户空间的收发固然不能并行,即在同一时刻发生,但可以并发,即多条连接的多个消息可以同时排队等待被收发)。
zmq.rs 则是用 Rust 语言重新实现的一个实验性质的 ZeroMQ 库,自然也(想)用到异步并发模型。Rust 语言本身曾经内置了基于 libuv 和协程的异步并发模型(M:N),所以早先写的 zmq.rs 是基于老版本的 Rust 的异步并发模型的。但在 1.0 前后,Rust 移除了 M:N,所以 zmq.rs 面临着一次重写,而因为个人时间原因,还没写,不好意思。
关于Rust 异步并发的那些事儿,这得从协程开始说,了解的同学请再次卧倒。
前面提到了,在 EventLoop 中异步执行的事件处理逻辑。假想一下,如果逻辑是这样的:从 A 和 B 各接收一个数字,加起来发给 C 然后保存结果,那么这段逻辑要拆成 4 个函数:
只是因为,EventLoop 一次循环只能执行一段顺序逻辑——也就是一个函数,而需要多次 I/O 交互才能完成的逻辑就只能通过多个回调函数串在一起来表达。如果逻辑再复杂一点,就有专业术语来描述这种情形了,叫做 callback hell,比如生写 JavaScript 动画或者老版本的 Twisted 都生动展现过这一情形。
使用协程可以把以上 4 个函数通过一个函数(或者叫协程)来表达(借用一下 Python 3.5 的语法):
a = await A.recv() b = await B.recv() sum = a + b await C.send(sum) return sum
一下就清楚多了吧。使用协程并没有改变其在 EventLoop 中的执行本质,但允许用户以顺序编程的自然表达方式去实现实则是非阻塞的网络调用。这里神奇的地方是 await,它起到了把原本的两个回调函数串在一起的作用,执行到 await 的时候,当前协程的执行进度——即栈信息——会被交换到内存里,EventLoop 会完成当前的循环进入接下来的很多个循环,直到 await 的事件有了结果——比如 A 的消息到了——才会把回到协程(resume)的操作放入队列,在 EventLoop 执行到 resume 的时候,才会把暂存出来的栈信息交换回寄存器,然后继续往下执行比如把收到的数据赋值给 a。抛开 EventLoop,协程其实是就一段普通程序,只不过程序本身可以主动暂停自己的执行,同时让另一个协程恢复执行,在恢复的时候还可以传一个值过去,而这些都是发生在同一个操作系统线程里面的。配合上 EventLoop,原本的异步并发就被增强成了:在一个操作系统线程里(即前述 worker),多个协程以主动协作式的方式并发执行。描述出来就是,运行在根协程里的 EventLoop 根据事件调度,把执行权交给不同的协程,协程执行到 await 或者结束时会把执行权再交回给 EventLoop。因此,在一个操作系统线程中,同一时刻只有一个协程在执行,多个协程只是并发而并非并行。
前面提到过的 M:N 中的 M 指的就是 M 个协程,N 则是 N 个操作系统线程,也就是 M 个协程跑在 N 个操作系统线程上。在 1.0 之前一段时间,Rust 内置了 libuv 作为 EventLoop,libgreen 则是协程支持和调度,M:N 模型曾经是 Rust 语言的默认配置。一个 Task 就是一个协程,也就是 M Tasks : N Threads。 zmq.rs 就大概是这个时期写的,大概长这样:
用户拥有 ZmqSocket 对象,而其下的所有网络操作都是放在单独的 Task 里面实现的,与 ZmqSocket 通过 Channel 交换信息,比如图示中的 TcpListener,用户调用一次 bind 就会创建一个新的 TcpListener,而 TcpListener 做的工作也非常简单,在死循环里不断地接受新连进来的客户端连接,然后把连接对象通过 Channel 发还给 ZmqSocket。这样的设计十分简单粗暴,但在 M:N 的模型下也讲得通,而且效率和并发数从理论上来讲应该是相当理想的。因为,协程库在 Rust 里之所以叫做 libgreen,因为协程应该是轻量级的,绿色的,内存开销极少的,随随便便创建个几百亿(夸张)应该是轻松无压力的,而且协程间的切换应该也是非常快的(在数量级上比操作系统线程切换快)。
后来由于需要和语言定位问题,开发人员增加了 1:1 模型,也就是没有 EventLoop 和协程的普通同步阻塞模型,类似生写 C 代码也是 1:1 模型,Rust 里的话也就是 1 Task : 1 Thread。再后来,1:1 顶替 M:N 成为 Rust 运行时的默认模型, zmq.rs 需要单独配置才能享受 M:N 了。再再后来,M:N 被删掉了,Task 被改名为 Thread。
被删掉其实也是对的,因为 Rust 的运行时一直想要从接口上统一 1:1 和 M:N 两种实现,但是其实这俩的差异还是很大的,统一的美好理想在巨大的成本现实面前变得飘渺,再加上 Rust 语言本身的一些内情——相较于操作系统线程,libgreen 并不 green,内存开销并不比操作系统线程小多少——虽然切换的低成本优势仍在(没说错吧?),但是从长计议,libgreen 从标准库中被剔除,成为了一个没人维护的第三方库。至此, zmq.rs 也没法写下去了,除非自己实现一遍异步并发的模型(更别提协程了),否则随随便便开几千个线程也是醉了。很早之前的 Rust 官网首页介绍中,是有 Concurrency 字样的,现在再去看看呢,木有了。
历史就说这些,接下来是后来发生的事情和将来打算要做的东西。
没有异步网络库怎么能行,Rust 的生态社区还是很给力的,很快便有人填补了这一空白。MIO 类似于 libuv,专门为 Rust 语言提供 EventLoop 和异步网络支持;我们中国社区(Rust.cc)的 ELTON(也在群里)接着做了 coroutine-rs,专门为 Rust 语言提供协程支持;后来又有人写了 mioco,把 MIO 和 coroutine-rs 结合在了一起(虽然目测 ELTON 同学想要自己写一个更高端的调度器嘿嘿)。所以,接下来 zmq.rs 要做的就是迁移迁移,用这些新工具再次重新实现一遍。
流浪隱:Rust作为Mozilla开发的语言,发展会不会受到Mozilla限制那?
王川: Mozilla 确实不富有,但有些很优秀的开源软件和社区都没有资金后台,也可以非常出色;另外,Mozilla 感觉对社区的限制并不大,更多的还是社区自主在往前走
wqz:Rust想要增加市场份额,未来从哪些行业发力比较靠谱呢?
王川: 系统级语言的定位,我觉得还是从最适合的领域开始做咯,比如操作系统、基础库、实时系统/工具/平台、游戏等等。
浮影:通过多线程异步并行计算来将simulink离线模型转化为实时模型是否可行?
王川: 我想您问的是通过 M:N 来做实时哈。基于 EventLoop 协作式的这种模型,对于通常的实现在实时性上是没有保障的,比如 callLater 说 1 秒之后做什么,EventLoop 只能保证做的事情不会在 1 秒之内发生,因为有可能别的 callback 在做一个死循环。
后续 CSDN Rust 学习交流群会邀请更多的大牛来进行分享,如果你想实时听课和提问,请加群主微信 qshuguang2008 或扫描下方二维码被邀请进群,备注:实名+公司名+Rust。
第一期回顾:
精彩专访回顾: