文章目录
- 前言
- 一、创建线程
- 二、mpsc多生产者单消费者模型
- 1.创建一个简单的模型
- 2.分批发送数据
- 3. 使用clone来产生多个生产者
- 三、共享状态:互斥锁
- 1. 创建一个简单的锁
- 2. 使用互斥锁解决引用问题
前言
并发编程(Concurrent programming),指的是程序的不同部分相互独立的执行。而并行编程(parallel programming)代表程序不同部分于同时执行,这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因,在此类编程中一直是困难且容易出错的:Rust
希望能改变这一点。
在大部分现代操作系统中,已执行程序的代码在一个
进程(process)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 线程(threads)。
一、创建线程
Rust创建线程是通过thread::spawn函数来创建的,我们只要通过这个函数,并且传入一个闭包即可创建出一个线程。
以两个线程同时输出数字为例,主线程输出1到5,子线程输出1到10。其代码如下所示
thread::spawn
函数内的闭包就是子线程的内容,以外的全都是主线程的内容。
thread::sleep
是一个线程延时的函数,需要传入Duration
,Duration
是个表示时间单位的,这里Duration::from_millis(1)
代表1毫秒,Rust支持更加精确的时间,你可以去看一下这个类,支持微秒
,纳秒
等。
thread::spawn(|| {for i in 1..10 {println!("数字是:{},来自spawn创建的线程", i);thread::sleep(Duration::from_millis(1));}});for i in 1..5 {println!("数字是:{},来自主线程线程", i);thread::sleep(Duration::from_millis(1));}
这样就创建好一个线程了,执行效果如下图
从图中可以看出主线程输出了从1到4的数字编号,因为到了5的时候已经执行完毕了,但是子线程却是执行到了5,并没有向后执行,这是因为主线程已经结束了,此时会把子线程也销毁,简而言之就是子线程的生存周期超出了主线程的生存周期,主线程提前结束。
原因:无法保证其执行顺序,主线程提前结束
为了解决这个,就需要接收这个线程的返回值,然后调用join方法来让主线程等待子线程执行完毕再结束运行。
thread::spawn返回一个JoinHandle,其有一个join方法,可以让主线程等待该线程完毕后再结束。
JoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。join 放在循环之前可以实现先执行子线程【这种操作会变得同步,影响程序运行,因此需要方队地方,仔细斟酌】
处理以上的问题,因此对代码进行一下改动
let handle =thread::spawn(|| {for i in 1..10 {println!("数字是:{},来自spawn创建的线程", i);thread::sleep(Duration::from_millis(1));}});for i in 1..5 {println!("数字是:{},来自主线程线程", i);thread::sleep(Duration::from_millis(1));}handle.join().unwrap();
此时代码正常运行,结果如下图所示
如果将handle.join().unwrap();
放到主线程for循环之前则会出现同步运行的效果,如下图所示
move关键字主要的作用是用来避免在使用闭包的时候遇到的所有权问题的,它起到的一个作用就是在你代码有疑问时,会提示你代码哪里错了,为什么错了。当然,你也可以理解为捕获了外部的变量的所有权。
move关键字的使用是相当简单的,只要在闭包前面加上move就可以了,会自动进行变量的捕获。示例代码如下所示
let v = vec![1, 2, 3];let handle = thread::spawn(move || {println!("Here's a vector: {:?}", v);});handle.join().unwrap();
二、mpsc多生产者单消费者模型
mpsc
是 多个生产者,单个消费者
(multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的 发送
(sending)端,但只能有一个消费这些值的 接收
(receiving)端。
1.创建一个简单的模型
mpsc
是通过mpsc::channel
来创建的,返回一个发送者
(tx),一个接收者
(rx)。
创建一个简单的模型代码如下,发送端在线程内部发送一个字符串Hello
,然后接收端接收这个字符串。
let (tx, rx) = mpsc::channel();thread::spawn(move || {let hello = String::from("Hello");tx.send(hello)});let received = rx.recv().unwrap();println!("接收到的数据是:{}", received)
代码运行效果如下
2.分批发送数据
在使用时,发送端可能会发送多次数据,或者是分批来发送数据,如果再使用
let received = rx.recv().unwrap();
来接收数据,则会起不到想要的效果,因为它只能接收一次,所以可以使用for循环来接收数据
for received in rx {println!("接收到的值: {}", received);}
这样只要是发送端发送的数据,接收端就都能接收到了,详细代码如下
let (tx, rx) = mpsc::channel();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});// 不再显式调用 recv 函数for received in rx {println!("接收到的值: {}", received);}
3. 使用clone来产生多个生产者
let (tx, rx) = mpsc::channel();let tx1 = tx.clone();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx1.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});thread::spawn(move || {let vals = vec![String::from("more"),String::from("messages"),String::from("for"),String::from("you"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
三、共享状态:互斥锁
如果你对多线程开发有所了解,就一定了解过锁的概念。
1. 创建一个简单的锁
let m = Mutex::new(5);{// 使用 lock 方法获取锁,以访问互斥器中的数据。// 如果另一个线程拥有锁,并且那个线程 panic 了,则 lock 调用会失败。// MutexGuard也提供了一个 Drop 实现当离开作用域时自动释放锁let mut num = m.lock().unwrap();*num = 6;}println!("Mutex:{:?}", m);
2. 使用互斥锁解决引用问题
let counter = Arc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Arc::clone(&counter);let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num +=1;});handles.push(handle);}for handle in handles {handle.join().unwrap()}println!("Result: {}", *counter.lock().unwrap());