博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Rust 语言学习笔记(四)—— I/O
阅读量:6412 次
发布时间:2019-06-23

本文共 2615 字,大约阅读时间需要 8 分钟。

写在前面:这是一篇近一年前的草稿了,翻出来发现,关于 Task(已改名为 Thread)退出的一些做法仍然适用,而且 0.2 不出意外也要用到,所以仍然把这篇写完贴出来备查。但请注意,文中关于 libgreen 的一些描述已不属实。

这一篇隔的时间比较长,期间我们的游戏在准备上线,所以也没时间写 Rust,着重在课余时间研读了各种文档、源码和 issue report——关于 Rust 的 I/O。

从试图杀掉一个 Task 开始

我的 项目终于开始碰到网络操作了。由于 Rust 给封装出来的 I/O 接口都是相对于 Task 同步的,所以目前来看还得给每一批 I/O 操作创建一个 Task

这里得多插一点内容了。首先是关于 ZeroMQ,它的一个 socket 是可以跟很多个别的 ZMQ socket 通讯的,而且是可以通过不同的 endpoint 来完成。比如一个 REP 服务端 socket,同时监听了 8080 和 9090 两个端口(endpoint),每个端口可能都有数十个 REQ 客户端 socket 连接上去;更有甚者,这个 REP socket 也可以主动去连接某些客户端 socket 监听的 endpoint,上门服务。而在所有这些网络拓扑结构的上面,一个 REP socket 对程序员的接口是始终一致的,您只需要反复地从这个 REP socket 读一个数据包,然后发一个数据包就好了。

这样一来呢,我就得给 REP socket 底层的每一个 TCP socket 连接创建至少一个 Task,以满足其并发性。之前我们有提到,Rust 提供了 libgreenlibnative 两种运行时环境,对应了两种不同的 Task 实现模型。对于 来说,基于目前的阻塞式的 I/O 接口来看,我们很有可能需要创建大量的 Task——这对于 libgreenM:N 模型来说是轻而易举的,但对于 libnative 来说却是值得商榷的,因为 1:1 的模型意味着我们将会用大量操作系统的线程来微操极少量的 ZMQ socket,这对于追求高并发的 ZMQ 也许并不是一个好主意——虽然 Rust 的 Task 模型能极大地避免,但是内存占用会不会太高(哈!高!-2015),上下文切换的代价会不会太大等问题还有待于进一步测试。如果结果不理想,也许每批 I/O 一个 Task 的这种设计就要被推倒,新的设计将需要 Rust 提供异步的 I/O 接口。(0.2 确实将这么改 -2015

回到原来的话题。因为创建了好多 Task,一定会碰到的问题就是怎么结束它们,所以我一上来就打算先看一眼这个问题。没想到这一看,看出了好多问题。

因为受 Python 的严重影响,我自然而然地以为,结束一个 Task 应该用 kill()——对于一个暂停状态的微线程,扔进去一个 GreenletExit 异常是多么正常的一种方式。可是 Rust 不那么认为。我也想到了由于需要支持 libnative,停止一个阻塞在 I/O 调用中的线程绝非扔一个 GreenletExit 那么简单,但我还是义无反顾地去搜各种 rust task kill terminate shutdown 之类的关键词。

结果逐步明朗,原来 Rust 在 0.9 之前确实有过 Task 的 ,是通过 supervisor 模型实现的——即一对 Task,任何一个挂掉都会导致另一个挂掉。但是呢,由于这个功能被砍掉了,也就是说,在 Rust 里,一个 Task 只能从内部抛错误死掉,没有办法从外部直接杀掉。另外,这个还(居然!)导致了。

正确结束一个 Task

既然无法从别的 Task 中主动杀掉一个 Task,那么就想办法让这个 Task 自杀。一个长时间运行的 Task 通常处于两种状态:1、执行代码;2、等待事件。

对于一个正在执行代码的 Task,我们是无法让 Task 自己忽然想到该结束了然后戛然而止。只有在某些特殊情况下,我们才能手工写一些代码,让程序执行一段代码之后,去检查一下是否应该结束了,比如在一个死循环里:

rustwhile self.running {    // Do everything else}

而大多数其他情况下,从事件等待中跳出来结束一个 Task 更为常见。这里也分两种情况:A、等待 I/O 事件;B、等待 channel 事件。两种情况处理都比较简单,A 的话就给调用加一个稍短的超时,然后重复前面的那个例子,比如:

rusttcp_stream.set_read_timeout(Some(1000));while self.running {    let result = tcp_stream.read_byte();    // Do the rest}

而对于等待一个 channelTask 来说就更容易了,只要 channel 的另一端销毁了,这个等待的调用就会自动结束,只需要正确处理调用结果就好了。

其实,上面两个例子中的 self.running 应(至少)为一个 Arc,因为需要从别的 Task 中来设置这个值。这里还有一种也是用 channel 的处理方式,就是在每次循环的开始处,向一个连接到父 TaskchannelSender 端,发送一个空白消息,这样如果另一端已经销毁了,发送会失败,也就意味着我们该退出这个 Task 了,比如这样:

rustlet (tx, rx) = channel();// ... in tasklet mut a = TcpListener::bind("127.0.0.1:8482").listen().unwrap();a.set_timeout(Some(1000));loop {    match a.accept() {        Ok(s) => { tx.send(Some(s)); }        Err(ref e) if e.kind == TimedOut => { tx.send(None).unwrap(); }        Err(e) => println!("err: {}", e), // something else    }}

转载地址:http://cikra.baihongyu.com/

你可能感兴趣的文章
LNMP架构介绍、MySQL安装、PHP安装、 Nginx介绍
查看>>
简单的Spark+Mysql整合开发
查看>>
阿里java面试经验大汇总(附阿里职位需求)
查看>>
Python全套零基础视频教程+软件2018最新编程视频!
查看>>
内存管理之1:x86段式内存管理与保护模式
查看>>
20180925上课截图
查看>>
IO输入/输出流的简单总结
查看>>
JavaScript之DOM-9 HTML DOM(HTML DOM概述、常用HTML DOM对象、HTML表单)
查看>>
技术成长之路(一)
查看>>
中国北方国际五金城硬件选型
查看>>
php.exe启动时提示缺少MVCR110.dall 64位 window系统 解决
查看>>
判断是否为数字方法
查看>>
[翻译] EF Core in Action 关于这本书
查看>>
js Uncaught TypeError: undefined is not a function
查看>>
数据库存储引擎
查看>>
[2019.2.13]BZOJ4318 OSU!
查看>>
版本号带两个小数点的,如何比较大小?( NSStringCompareOptions )
查看>>
QCustomplot使用分享(三) 图
查看>>
什么是java?
查看>>
WPF路径动画(动态逆向动画)
查看>>