4.6 并发
Page 100
并发和并行在计算机科学中是非常重要的主题.在工业领域也很火.计算机如今有越来越多的核心,然而很多程序员还没有准备好使用它们.
Rust安全的内存特性同样适用于并发存储.并发的Rust代码也是内存安全的,没有数据竞争.Rust的类型系统会保证这一点,给你提供了有利的帮助在编译时实现并发代码.
在我们开始讨论Rust的并发之前,我们需要理解一个很重要的事情:Rust是一个低级别语言,它的所有功能都是通过库来提供的,而不是语言自身.也就是说你不喜欢Rust处理并发的方式,你完全可以实现一个你自己的版本.mio(https://github.com/carllerche/mio)就是一个真实世界的例子.
Page 101
背景:Send和Sync
并发是一个很难使用的技术.在Rust中,我们有一个很强的静态类型系统来帮助我们使用并发代码.为此,Rust提供了两种模板来实现并发.
Send: 第一种就是我们将要讨论的Send.当一个类型T实现了Send,它就告诉了编译器这个类型可以在多个线程安全传递变量所有性.
设置一些限制是很重要的.比如,如果我们有一个链接两个线程的通道,我们希望可以沿着通道将数据从一个线程传递到另一个线程.因此,我们要确保Send可以被这个类型实现.
相反,如果我们用FFI包装了一个不是线程安全的库,我们就不会要实现Send,编译器也就会帮我们确保它不会离开当前线程.
Sync: 第二种就是Sync.当一个类型T实现了Sync,它就告诉了编译器这个类型在使用多线程的时候不会引入不安全的内存.
例如,共享一个不可变数据的原子引用计数就是类型安全的.Rust提供了一个类型Arc<T>,他就实现了Sync,所以它可以安全的在两个线程之间共享.
这两种模板可以保证你的并发代码工作正确.在我们解释原因之前,我们需要先学习一下如何创建一个Rust并发程序!
线程:
Rust标准库提供了一个线程库,它可以运行并发的代码.这有一个基本的例子来使用std::thread:
use std::thread;
fn main() {
thread::spawn(|| {
println!("Hello from a thread!");
});
}
thread::spawn()函数接受一个闭包,这个闭包在一个新的线程被执行.它返回一个该线程的句柄,它可以等到子线程结束的时候来执行它的结果:
use std::thread;
fn main() {
let handle = thread::spawn(|| {
"Hello from a thread!"
});
println!("{}", handle.join().unwrap());
}
许多语言都有执行多线程的能力,但是大部分都不是安全的.有很多书都讲了如果防止共享可变数据的时候发生错误.Rust类型系统也提供了这个能力,它是从编译时就保证了不会发生数据竞争.让我们来讨论一下如果再多线程之间共享数据吧.
安全共享可变状态:
基于Rust的类型系统,我们有一个听起来像谎言的概念:"安全共享可变状态."许多程序员都同一个观点:共享可变状态是非常非常糟糕的.
有人曾经这样说:
共享可变状态是万恶之源.大多数语言都尝试用"可变的"部分来处理它,但是Rust语言用"共享的"来处理它.
Rust语言的所有权系统可以防止指针的误用,同样可以防止数据竞争,那是并发中最糟糕的bug.
作为一个例子,这有一个Rust程序,在多数语言中会发生数据竞争.它无法编译:
use std::thread;
fn main() {
let mut data = vec![1u32, 2, 3];
for i in 0..3 {
thread::spawn(move || {
data[i] += 1;
});
}
thread::sleep_ms(50);
}
这会有一个错误:
Page 103
在此例中,我们知道我们的代码是安全的,但是Rust不能确保.而且它实际上并不安全:如果我们在每个线程都有一个data的引用,该线程又取得了引用的所有权,那么我们就有3个所有者了!这很糟.我们可以通过Arc<T>类型来解决这个问题,它是一个原子的引用计数指针.原子的意思是它在多线程共享的时候是安全的.
Arc<T>提供了一个额外的属性来确保它在多线程共享的时候是安全的:它认为它的内容是Sync.但是在我们的例子中,我们需要改变变量的值.我们需要一个类型能保证只有一个人在同一时刻可以改变它的值.为此,我们可以试用Mutex<T>类型.第二个版本的例子仍然不能工作但是原因不一样:
use std::thread;
use std::sync::Mutex;
fn main() {
let mut data = Mutex::new(vec![1u32, 2, 3]);
for i in 0..3 {
let data = data.lock().unwrap();
thread::spawn(move || {
data[i] += 1;
});
}
thread::sleep_ms(50);
}
这里是错误信息:
Mutex和lock方法有如下签名:
fn lock(&self) -> LockResult<MutexGuard<T>>
因为Send没有被MutexGuard<T>实现,所以我们不能传递这个guard超越线程边界,所以我们得到了这个错误.
我们可以使用使用Arc<T>来解决.这个版本可以工作了:
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
}
thread::sleep_ms(50);
}
我们现在在Arc类型上调用clone()方法,他可以增加内部计数.这个句柄会被移入新的线程.我们来看看线程内部的代码:
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
首先,我们调用lock()方法,它获得了mutex的锁.因为这个操作可能会失败,所以它返回了一个Result<T, E>类型,又因为这仅仅是个例子,我们直接调用unwrap()来得到data的一个引用.真实的代码应该包含更多的健壮的错误处理逻辑.因为有了锁,我们现在可以随意的改变它的值.
最后,当线程还在运行的时候,我们等待一段很短的时间.但是这还不理想:我们本可以选择一个更合理的时间等待,但是我们更有可能等的不够久或太久了,这取决于这个线程到底要多久才能完成我们的任务.
一个更精简的例子使用了Rust标准库提供的一个机制来同步多个线程.让我们来讨论其中的一种机制:channels.
Page 105
Channels:
这个例子使用了channels来同步多个线程,而不是等待一个特定的时间:
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
fn main() {
let data = Arc::new(Mutex::new(0u32));
let (tx, rx) = mpsc::channel();
for _ in 0..10 {
let (data, tx) = (data.clone(), tx.clone());
thread::spawn(move || {
let mut data = data.lock().unwrap();
*data += 1;
tx.send(());
});
}
for _ in 0..10 {
rx.recv();
}
}
我们使用mpsc::channel()方法来构造了一个channel.我们仅仅send了一个空的()到channel中,然后等待他们10个线程返回.
当这个channel正在发送通用信号的时候,我们就可以发送任何数据到channel中!
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
for _ in 0..10 {
let tx = tx.clone();
thread::spawn(move || {
let answer = 42u32;
tx.send(answer);
});
}
rx.recv().ok().expect("Could not receive answer");
}
一个u32被发送了.我们创建一个线程,他会计算结果,然后它通过channel会send()给我们答案.
Panics
一个Panic!会让当前执行线程崩溃.你可以试用Rust线程实现一个简单的孤立机制:
use std::thread;
let result = thread::spawn(move || {
panic!("oops!");
}).join();
assert!(result.is_err());
分享到:
相关推荐
rust自动翻译中文rust自动翻译中文,除
欢迎阅读!这本书将教会你使用Rust编程语言。
Rust 核心库和标准库中文翻译 Rust 核心库和标准库中文翻译 Rust 核心库和标准库中文翻译 Rust 核心库和标准库中文翻译 Rust 核心库和标准库中文翻译 ...
官网最新翻译。
Rust 代码和资源的精选列表中文翻译
官网最新翻译。
中文版(Rust by Example)Rust社区官方翻译,翻译精准可靠,非常适合Rust入门学习,主要讲述Rust基本语法
Rust Reference 的中文翻译,预期能及时和原文保持同步rust-reference-master.zip
Steve Klabnik 和 Carol Nichols,以及来自 Rust 社区的贡献(Rust 中文社区翻译) 本书假设你使用 Rust 1.37.0 或更新的版本,且在所有的项目中的 Cargo.toml 文件中通过 edition="2018" 采用 Rust 2018 Edition ...
Rust 核心库和标准库中文翻译,可作为 IDE 工具的智能提示,并生成本地 API 文档
Rust 程序设计语言 简体中文版 ---------------------------------------------------- 本 ePub 基于开源文档,目录书签齐全。 版权归原作者,翻译版权归译者。 --------------------------------------------------...
官网最新翻译。
中文翻译:<rust-lang/rustlings> 帮你扶住 Rust 的那双手 ❤️ ✅
Rustc Dev Guide 中文翻译 的中文翻译已经启动。因为原项目还在变动期,为了翻译方便,所以此翻译项目组织结构就不和原项目保持一致了。志愿者招募要求:热爱 Rust,对 Rust 已经有一定了解想深入了解 Rust 编译器想...
本文档为 The Rust Programming Language 的中文翻译。
Rust编程语言 rust-book 中文翻译 (进行中...) 本项目是对的中文翻译, 力求语言准确,正确还原原文内容,规避任何看起来像机器翻译的句子。 完整书籍: github: 勘误:
:globe_with_meridians: 这是《 The Rust Programming Language》一书的法文翻译 翻译在文件夹中。 其他所有内容都应保留在英语书中(某些必需的文件除外,例如README.md) 。 想要帮助翻译吗? 请阅读文件 ! ...
注1:《Rust Cookbook 中文版》翻译自 rust-lang-nursery 团队撰写的 "A Rust Cookbook",感谢 rust-lang-nursery 团队的无私奉献!注2:《Rust Cookbook 中文版》计划为两个阶段:第一阶段:经仔细斟酌,形成专业、...
目前翻译到第十六章, 还在翻译中。。。。。。。。。。
Rust 核心库和标准库中文翻译,可作为 IDE 工具的智能提示,并生成本地 API 文档