我写这篇短文的时候,正值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语言的多线程编程的更多相关文章

  1. C语言 之 多线程编程

    一.基础知识 计算机的核心是CPU,承担了所有的计算任务. 操作系统是计算机的管理者,负责任务的调度.资源的分配和管理,统领整个计算机硬件. 应用程序则是具有某种功能的程序,程序是运行于操作系统之上的 ...

  2. linux下c语言的多线程编程

    我们在写linux的服务的时候,经常会用到linux的多线程技术以提高程序性能 多线程的一些小知识: 一个应用程序可以启动若干个线程. 线程(Lightweight Process,LWP),是程序执 ...

  3. C# 语言的多线程编程,完全是本科OS里的知识

    基本知识,无参数Thread和带参数的Thread Thread类的参数就是参数指针,可以传入一个无参的函数. 如果要传入带参数的函数,先new一个ParameterizedThreadStart委托 ...

  4. linux下C语言多线程编程实例

    用一个实例.来学习linux下C语言多线程编程实例. 代码目的:通过创建两个线程来实现对一个数的递加.代码: //包含的头文件 #include <pthread.h> #include ...

  5. C语言中的多线程编程

    很久很久以前,我对C语言的了解并不是很多,我最早听说多线程编程是用Java,其实C语言也有多线程编程,而且更为简单.方便.强大.下面就让我们简单领略一下Unix C语言环境下的多线程编程吧! 下面先看 ...

  6. C语言多线程编程

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUT ...

  7. C语言使用pthread多线程编程(windows系统)二

    我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你最好是选择POSIX中的Pthread函数库,我 ...

  8. Linux C语言多线程编程实例解析

    Linux系统下的多线程遵循POSIX线程接口,称为 pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a.顺便说一下,Linux ...

  9. Rust语言:安全地并发

    http://www.csdn.net/article/2014-02-26/2818556-Rust http://www.zhihu.com/question/20032903 Rust是近两年M ...

随机推荐

  1. 【Tomcat】配置Tomcat

    写这篇博文的原因:因为发布Maven项目的时候,始终无法访问.所以顺便重新配置了Tomcat. 1.首先到官网下载一个Tomcat7版本的zip包,解压后,放入C盘(根据个人需求放置). 2.修改co ...

  2. 导入maven工程错误

    有时候导入maven工程会报空指针异常: An internal error occurred during: “Updating Maven Project”. java.lang.NullPoin ...

  3. 修改jetty的默认端口号

    jetty默认端口是8080,修改端口号也很简单,首先进入到jetty服务器安装目录下会看到start.ini配置文件,这里就是jetty启动时加载的配置,其中包括要加载的模块,超时时间配置还有这里的 ...

  4. [iOS] 为文本加上横线方法

    _oldPriceLabel.text = "; _oldPriceLabel.textColor = [UIColor lightGrayColor]; NSMutableAttribut ...

  5. jquery中on绑定事件

    之前项目中动态创建的标签元素  在绑定事件的时候  都是无效  无论如何都不能触发 eg:在页面加载完成之后   再由脚本动态创建的<div>元素  在绑定事件的时候 例如click事件 ...

  6. ios 项目的.gitignore

    git作为代码管理工具,.gitignore文件用来忽略哪些哪些文件不用添加到仓库管理https://www.gitignore.io/ 这个网址输入变成语言会帮你生成常用的忽略文件如:IOS项目,输 ...

  7. c#线程间操作无效: 从不是创建控件“textBox1”的线程访问它

    线程开始前: Control.CheckForIllegalCrossThreadCalls = false;

  8. JHChart iOS图表工具库1.0.3新版本详解

    前言. 从2016年4月14日开始,本人着手开发了JHChart图表工具库.经过断断续续的开发,截止到现在,已经实现了折线图.柱状图.饼状图.环形图和表格样式的图表功能.为了方便使用,我已经将一个简单 ...

  9. Spring学习(二)

    1. AOP的思想(如何实现),AOP在哪些地方使用? 相关术语有哪些? AOP是面向切面编程,它是一种编程思想,采取横向抽取机制,取代了传统纵向继承体系重复性代码的方式 应用场景有: 记录日志 监控 ...

  10. AngularJs 通过 ocLazyLoad 实现动态(懒)加载模块和依赖

    好了,现进入正题,在 AngularJs 实现动态(懒)加载主要是依赖于3个主JS文件和一段依赖的脚本. 实现的过程主要是引用3个主要的JS文件 <script src="angula ...