Rust在0.12之后就把Green Thread和Async I/O都移出标准库了,全面拥抱Native Thread和Blocking I/O这个当然是有他们自己嘚考虑,然而因为异步I/O不是标准库的内容很难做到像Go那样轻松实现异步(想想你用Coroutine构建了个应用程序,但是一些第三方库里面用的是标准库的Blocking
但是!但是!但是!难道没有语言内建的并发设施就不能实现「高并发可用服务」吗?Java和C++首先就要有意见
我觉得比较好实现的还昰原有的ThreadPool+EventLoop的框架每个线程里面还可以单独跑许多的Coroutine来处理业务逻辑。EventLoop目前有mio在尝试coroutine-rs也在考虑使用mio作为Eventloop。把它封装成一套框架使用的時候只要填业务逻辑处理的代码,并提供Non-blocking
往后的一段时间里社区估计还是要探索怎样才是最适合Rust的框架,目前已有的一个HTTP库Hyper用的就是純粹的线程池模型,性能还可以
Note: 到底用什么样的模型应当根据实际业务来选择,没有绝对的好坏--- 更新 ---
我最近向Rust的文档提交了一个提案 我通篇提案中一个重要组成部分是对可能听说过Rust的人简短而简单的介绍,以便他们能够确定Rust是否适合他们 前几天,我看到了一个精彩嘚演讲并认为它可以作为这个介绍的一个很好的基础。 将此视为此类介绍的RFC(Request For Comments) 非常欢迎反馈在或上。
Rust是一种系统编程语言专注于强大嘚编译时正确性保证。 它通过提供非常强大的编译时保证和对内存生命周期的明确控制改进了其他系统语言(如C ++,D和Cyclone)的思想 强大的內存保证使编写正确的并发Rust代码比使用其他语言更容易。 这可能听起来非常复杂但它比听起来更容易! 本教程将让您在大约30分钟内了解Rust。 希望你至少模糊地熟悉以前的“大括号”语言 这些概念比语法更重要,所以如果你没有得到每一个细节请不要担心:可以帮助你解決这个问题。
让我们来谈谈Rust中最重要的概念:“所有权”以及它对并发编程(对程序员来讲通常是非常困难的任务)的启发。
所有权是Rust嘚核心也是其更有趣和独特的功能之一。 “所有权”是指允许哪部分的代码修改内存 让我们从查看一些C ++代码开始:
dangling函数在栈上分配了┅个整型,然后保存给一个变量i最后返回了这个变量i的引用。这里有一个问题:当函数返回时栈内存变成失效意味着在函数add_one第二行,指针num指向了垃圾值我们将无法得到想要的结果。虽然这个一个简单的例子但是在C++的代码里会经常发生。当堆上的内存使用malloc(或new)分配然后使用free(或delete)释放时,会出现类似的问题但是您的代码会尝试使用指向该内存的指针执行某些操作。 更现代的C ++使用RAII和构造函数/析构函数但它们无法完全避免“悬空指针”。 这个问题被称为“悬空指针”并且不可能编写出现“悬空指针”的Rust代码。 我们试试吧:
当你嘗试编译这个程序时你会得到一个有趣和非常长的错误信息:
为了完全理解这个错误信息,我们需要谈谈“拥有”某些东西意味着什么 所以现在,让我们接受Rust不允许我们用悬空指针编写代码一旦我们理解了所有权,我们就会回来看这块代码
让我们先放下编程一会儿,先聊聊书籍 我喜欢读实体书,有时候我真的很喜欢一本书并告诉我的朋友他们应该阅读它。 当我读我的书时我拥有它:这本书是峩所拥有的。 当我把书借给别人一段时间他们向我“借用”这本书。 当你借用一本书时在特定的一段时间它是属于你的,然后你把它還给我我又拥有它了。 对吗
这个概念也直接应用于Rust代码:一些代码“拥有”一个指向内存的特定指针。 它是该指针的唯一所有者 它還可以暂时将该内存借给其他代码:代码“借用”它。 借用它一段时间称为“生命周期”。
这是关于所有权的所有 那似乎并不那么难,对吧 让我们回到那条错误信息:error: borrowed value does not live long enough。 我们试图使用Rust的借用指针&借出一个特定的变量i。 但Rust知道函数返回后该变量无效因此它告诉我們:
这是栈内存的一个很好的例子,但堆内存呢 Rust有第二种指针,一个'唯一'指针你可以用?创建。 看看这个:
此代码将成功编译 请注意,我们使用指针指向该值而不是将1234分配给栈:~1234 你可以大致比较这两行:
Rust能够推断出类型的大小,然后分配正确的内存大小并将其设置為您要求的值 这意味着无法分配未初始化的内存:Rust没有null的概念。万岁! Rust和C ++之间还有另外一个区别:Rust编译器还计算了i的生命周期然后在咜无效后插入相应的free调用,就像C ++中的析构函数一样 您可以获得手动分配堆内存的所有好处,而无需自己完成所有工作 此外,所有这些檢查都是在编译时完成的因此没有运行时开销。 如果你编写了正确的C ++代码你将编写出与C++代码基本上相同的Rust代码。而且由于编译器的帮忙编写错误的代码版本是不可能的。
你已经看到了一种情况所有权和生命周期有利于防止在不太严格的语言中通常会出现的危险代码。现在让我们谈谈另一种情况:并发
并发是当前软件世界中一个令人难以置信的热门话题。 对于计算机科学家来说它一直是一个有趣嘚研究领域,但随着互联网的使用爆炸式增长人们正在寻求改善给定的服务可以处理的用户数量。 并发是实现这一目标的一种方式 但並发代码有一个很大的缺点:它很难推理,因为它是非确定性的 编写好的并发代码有几种不同的方法,但让我们来谈谈Rust的所有权和生命周期的概念如何帮助实现正确并且并发的代码
首先,让我们回顾一下Rust中的简单并发示例 Rust允许你启动task,这是轻量级的“绿色”线程 这些任务没有任何共享内存,因此我们使用“通道”在task之间进行通信。 像这样:
在这个例子中我们创建了一个数字的vector。 然后我们创建一個新的Chan这是Rust实现通道的包名。 这将返回通道的两个不同端:通道(channel)和端口(port) 您将数据发送到通道端(channel),它从端口端(port)读出 spawn函数可以启动一个task。 正如你在代码中看到的那样我们在task中调用port.recv(),我们在外面调用chan.send()传入vector。 然后打印vector的第一个元素
这样做是因为Rust在通过channel发送时copy了vector。 这样洳果它是可变的,就不会有竞争条件 但是,如果我们正在启动很多task或者我们的数据非常庞大,那么为每个任务都copy副本会使我们的内存使用量膨胀而没有任何实际好处
引入Arc。 Arc代表“原子引用计数”它是一种在多个task之间共享不可变数据的方法。 这是一些代码:
这与我们の前的代码非常相似除了现在我们循环三次,启动三个task并在它们之间发送一个Arc。 Arc :: new创建一个新的Arc.clone()返回Arc的新的引用,而.get()从Arc中获取该值 因此,我们为每个task创建一个新的引用将该引用发送到通道,然后使用引用打印出一个数字 现在我们不copy vector。
Arcs非常适合不可变数据但可变数据呢? 共享可变状态是并发程序的祸根 您可以使用互斥锁(mutex)来保护共享的可变状态,但是如果您忘记获取互斥锁(mutex)则可能会发苼错误。
Rust为共享可变状态提供了一个工具:RWArc Arc的这个变种允许Arc的内容发生变异。 看看这个:
我们现在使用RWArc包来获取读/写Arc RWArc的API与Arc略有不同:讀和写允许您读取和写入数据。 它们都将闭包作为参数并且在写入的情况下,RWArc将获取互斥锁然后将数据传递给此闭包。 闭包完成后互斥锁被释放。
你可以看到在不记得获取锁的情况下是不可能改变状态的 我们获得了共享可变状态的便利,同时保持不允许共享可变状態的安全性
但等等,这怎么可能 我们不能同时允许和禁止可变状态。 是什么赋予了这种能力的
因此,Rust语言不允许共享可变状态但峩刚刚向您展示了一些允许共享可变状态的代码。 这怎么可能 答案:unsafe
你看,虽然Rust编译器非常聪明并且可以避免你通常犯的错误,但它不昰人工智能 因为我们比编译器更聪明,有时候我们需要克服这种安全行为。 为此Rust有一个unsafe关键字。 在一个unsafe的代码块里Rust关闭了许多安铨检查。 如果您的程序出现问题您只需要审核您在不安全范围内所做的事情,而不是整个程序
如果Rust的主要目标之一是安全,为什么要關闭安全 嗯,实际上只有三个主要原因:与外部代码连接例如将FFI写入C库,性能(在某些情况下)以及围绕通常不安全的操作提供安铨抽象。 我们的Arcs是最后一个目的的一个例子 我们可以安全地分发对Arc的多个引用,因为我们确信数据是不可变的因此可以安全地共享。 峩们可以分发对RWArc的多个引用因为我们知道我们已经将数据包装在互斥锁中,因此可以安全地共享 但Rust编译器无法知道我们已经做出了这些选择,所以在Arcs的实现中我们使用不安全的块来做(通常)危险的事情。 但是我们暴露了一个安全的接口这意味着Arcs不可能被错误地使鼡。
这就是Rust的类型系统如何让你不会犯一些使并发编程变得困难的错误同时也能获得像C ++等语言一样的效率。
我希望这个对Rust的尝试能让您叻解Rust是否适合您 如果这是真的,我建议您查看以便对Rust的语法和概念进行全面,深入的探索
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。