Rust语言的多线程编程
我写这篇短文的时候,正值Rust1.0发布不久,严格来说这是一门兼具C语言的执行效率和Java的开发效率的强大语言,它的所有权机制竟然让你无法写出线程不安全的代码,它是一门可以用来写操作系统的系统级语言,如果说新一代编程语言是什么,那就Rust了。
下面我注重介绍Rust的多线程编程是怎样,其中大部分内容参考翻译自Rust的官方文档,请看:
Concurrency并发
在计算机科学上,并发Concurrency 和并行 parallelism是非常重要的话题,也是软件产业一个热门的话题。计算机CPU有了越来越多的的核,但很多程序员没有准备好充分利用它们。
Rust的内存安全特性也应用于并发。Rust程序必须内存安全,没有数据竞争。Rust的类型系统 很胜任这工作,很容易让你理解在编译时的并行代码。
在谈论Rust并发特色之前,了解一些东西很重要:Rust是一门足够低级的语言,所有这些都由标准库提供,而不是语言本身。这意味着如果你你不喜欢Rust处理并发的某些方面,你可以用其它方式来实现。 mio 就是这方面实践的一个真实的例子。
背景: Send 和 Sync
并发很难解释清楚。在Rust中,我们由一个强大的静态的类型系统来帮助我们理解我们的代码。Rust本身给我们两个特性,帮助我们实现并发编程。
Send
第一个类是 Send. 当类型 T 实现了 Send, 它告诉编译器这个类型的实例的所有权可以在多个线程之间安全传递。
实施强制的限制条件是很重要的。例如,如果我们由一个通道在两个线程之间,我们可能会想在两个线程之间传递数据。因此,我们要保证传递的数据类型要实现 Send 特性。
相反,如果我我们用FFI包裹了一个线程不安全的类库,我们不会去实现 Send, 编译器会帮助我们确保它不会离开当前线程。
Sync
第二个特性是 Sync. 当一个类型实现了 Sync, 它向编译器表明这个类型的数据在多线程并发是不可能导致内存的不安全。例如,由原子引用计数的不可变数据的共享是线程安全的。Rust提供了一个类型 Arc<T>, 它实现了 Sync, 因此它可以在线程之间共享。
这两个特性运行你使用类型系统来保证你代码在并发情况下的所有权。在解释为什么之前,让我们先创建一段并行的Rust代码。
线程Threads
Rust标准库中的线程,允许你并行的运行Rust代码。下面是一个使用了 std::thread的例子:
use std::thread;
fn main() {
thread::spawn(|| {
println!("Hello from a thread!");
});
}
thread::spawn() 方法接受一个在新线程运行的闭包。spawn方法返回一个线程的处理对象,可以用来等待子线程结束和取得线程返回结果:
use std::thread;
fn main() {
let handle=thread::spawn(|| {
"Hello from a thread!"
});
println!("{}", handle.join().unwrap());
}
很多语言有执行多线程的能力,但是非常不安全。有很多关于如何防止共享状态数据导致错误的书籍。Rust通过在编译时防止数据竞争来解决这个问题。让我们谈论一下怎样真正地在线程之间安全地共享数据。
安全地共享可变状态
归功于Rust的类型系统,我们与一个看似谎言的概念:安全地共享可变状态。很多程序员都认同共享可变状态是非常非常糟糕的。
有人曾经说过:
共享可变状态是万恶的根源。大多数语言尝试从“可变”这个方向来解决这个问题,但Rust通过“共享”这方面来解决这个问题。
ownership system 帮助我们防止错误地使用指针,同样也帮助我们排除数据竞争。数据竞争是并发编程中最恐怖地bug之一。
举例说明,下面是一个Rust程序,里面有一个其它语言经常会出现地数据竞争。但是在Rust中是无法编译通过地:
use std::thread;
fn main() {
let mut data=vec![1u32, , ];
for i in .. {
thread::spawn(move|| {
data[i] +=;
});
}
thread::sleep_ms();
}
编译时,提示错误如下:
8:17 error: capture of moved value: `data`
data[i] += 1;
^~~~
在这种情况,从代码我们知道我们的代码应该是安全的,但是Rust不确定。事实上是不安全地,如果我们在多个线程中有 data 的引用,线程拿走了引用的所有权,我们就有了三个拥有者了。这是不行的。我们可以通过 Arc<T> 来改正,它是一个原子引用计数器指针。原子意思是在多线程共享是安全的。
Arc<T> 假定一个它的内容有多个所有权,但是仍然可以安全地共享。它假定它地内容是线程同步的。但在我们这中情况,我们向改变里面的数据。我们需要一个可以保证一次只能由一个线程改数据的类型。这个类型就是 Mutex<T> 。下面老师第二个版本的代码。虽然依然编译不通过,但是是不同原因:
use std::thread;
use std::sync::Mutex; fnmain() {
let mut data=Mutex::new(vec![1u32, , ]); for i in 0.. {
let data=data.lock().unwrap();
thread::spawn(move|| {
data[i] +=;
});
} thread::sleep_ms();
}
下面是错误信息:
<anon>:9:9: 9:22 error: the trait `core::marker::Send` is not implemented for the type `std::sync::mutex::MutexGuard<'_, collections::vec::Vec<u32>>` [E0277]
<anon>:11 thread::spawn(move || {
^~~~~~~~~~~~~
<anon>:9:9: 9:22 note: `std::sync::mutex::MutexGuard<'_, collections::vec::Vec<u32>>` cannot be sent between threads safely
<anon>:11 thread::spawn(move || {
^~~~~~~~~~~~~
你看看, Mutex 由一个 lock 方法,方法的签名是:
fn lock(&self) ->LockResult<MutexGuard<T>>
因为 MutexGuard<T>没有实现Send,我们不可以不能在线程之间传输这个对象,所以报错。
我们可以使用 Arc<T> 来修正这个错误。下面是可以编译通过的版本:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data=Arc::new(Mutex::new(vec![1u32, , ]));
for i in 0.. {
let data=data.clone();
thread::spawn(move|| {
let mut data=data.lock().unwrap();
data[i] +=;
});
}
thread::sleep_ms();
}
我们调用了Arc的 clone() 方法 ,增加了内部的引用计数。它们返回只移动到了一个新的线程。我们细看一下线程的主体:
thread::spawn(move|| {
let mut data=data.lock().unwrap();
data[i] +=;
});
首先,我们调用 lock(), 取得了一个互斥锁。因为可能会失败,它返回一个结果Result<T, E>, 因为只是举例说明,我们直接调用 unwrap() 来获取data的一个引用。真实代码可能要写更全面的代码来作错误处理。因为我们现在有一个互斥锁了,所以可以自由地改变数据。
最后,当线程运行,我们等一段时间,但是这是有点不切实际:等多久合适呢,很难猜测,这是程序运行CPU执行情况决定的。
一种更加精准的计时器是使用Rust标准库提供的机制来实现线程同步。让我们讲一下这中机制: channels.
通道Channels
下面代码是使用channel来同步,而不是漫无目的地等待:
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.. {
let (data, tx) = (data.clone(), tx.clone());
thread::spawn(move|| {
letmutdata=data.lock().unwrap();
*data+=;
tx.send(());
});
}
for _ in 0.. {
rx.recv();
}
}
我们使用mpsc::channel() 方法类构造一个channel。我们用10个线程分别向通道发送一个简单地() 然后在主线程接收。
send方法是泛型地,我们可以向通道发送任何类型地数据。
use std::thread;
use std::sync::mpsc; fn main() {
let (tx, rx) =mpsc::channel(); for _ in0.. {
let tx=tx.clone(); thread::spawn(move|| {
let answer=42u32; tx.send(answer);
});
} rx.recv().ok().expect("Could not receive answer");
}
一个 u32 数据被发送,因为我们可以复制一份。因此我们可以创建一个线程,叫它计算答案,然后通过将答案通过channel发送给我们。
Panics致命异常
一个 panic! 会使执行中地线程崩溃。你可以这样写:
use std::thread;
let result=thread::spawn(move|| {
panic!("oops!");
}).join();
assert!(result.is_err());
我们的线程返回了一个结果,我们可以通过这个返回结果检查线程师父抛异常。
Rust语言的多线程编程的更多相关文章
- C语言 之 多线程编程
一.基础知识 计算机的核心是CPU,承担了所有的计算任务. 操作系统是计算机的管理者,负责任务的调度.资源的分配和管理,统领整个计算机硬件. 应用程序则是具有某种功能的程序,程序是运行于操作系统之上的 ...
- linux下c语言的多线程编程
我们在写linux的服务的时候,经常会用到linux的多线程技术以提高程序性能 多线程的一些小知识: 一个应用程序可以启动若干个线程. 线程(Lightweight Process,LWP),是程序执 ...
- C# 语言的多线程编程,完全是本科OS里的知识
基本知识,无参数Thread和带参数的Thread Thread类的参数就是参数指针,可以传入一个无参的函数. 如果要传入带参数的函数,先new一个ParameterizedThreadStart委托 ...
- linux下C语言多线程编程实例
用一个实例.来学习linux下C语言多线程编程实例. 代码目的:通过创建两个线程来实现对一个数的递加.代码: //包含的头文件 #include <pthread.h> #include ...
- C语言中的多线程编程
很久很久以前,我对C语言的了解并不是很多,我最早听说多线程编程是用Java,其实C语言也有多线程编程,而且更为简单.方便.强大.下面就让我们简单领略一下Unix C语言环境下的多线程编程吧! 下面先看 ...
- C语言多线程编程
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUT ...
- C语言使用pthread多线程编程(windows系统)二
我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你最好是选择POSIX中的Pthread函数库,我 ...
- Linux C语言多线程编程实例解析
Linux系统下的多线程遵循POSIX线程接口,称为 pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a.顺便说一下,Linux ...
- Rust语言:安全地并发
http://www.csdn.net/article/2014-02-26/2818556-Rust http://www.zhihu.com/question/20032903 Rust是近两年M ...
随机推荐
- jQuery cookie使用
什么是jquery cookie? A simple, lightweight jQuery plugin for reading, writing and deleting cookies. Usa ...
- java--连接SQL数据库获取验证码
1.导入SQL相关的包: 可以下载:mysql-connector-java-5.1.39-bin.jar 将包导入到工程的方法:project(在工程名上点鼠标右键) -> Build Pat ...
- 重写jquery的ajax方法
//首先备份下jquery的ajax方法 var _ajax=$.ajax; //重写jquery的ajax方法 $.ajax=function(opt){ //备份opt中error和success ...
- toArray(),toJson(),hidden([ ]),visible([ ])
toArray() 转换为数组,hidden()不输出的字段 public function index(){ $user = model('User'); $data = $user::)-> ...
- 多预览小图焦点轮播插件lrtk
多预览小图焦点轮播插件lrtk // JavaScript Document $(document).ready(function(){ //$('#select_btn li:first').css ...
- JDBC、DAO
JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力 JDBC的工作原理 JDBC 驱动器由数据库厂商提供 1.在个人开发与测试中,可以使用JDBC-ODBC桥连方式 2.在生产型开发 ...
- WPF中运行时使内容可以上下左右被鼠标拖动应该怎么做?
<Window x:Class="testGridSplitter.MainWindow" xmlns="http://schemas.microsoft.com/ ...
- [Android Pro] 完美Android Cursor使用例子(Android数据库操作)
reference to : http://www.ablanxue.com/prone_10575_1.html 完美 Android Cursor使用例子(Android数据库操作),Androi ...
- Spring学习(三)
1,Spring的事务管理机制 Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的: l PlatformTransactionManager:事务管理器-主要用于 ...
- NYOJ之题目325 zb的生日
-------------------------------------- 刷一辈子水题... AC代码: import java.util.Scanner; public class Main { ...