并发
Concurrent:程序的不同部分之间独立的执行
Parallel:程序的不同部分同时运行
Rust无畏并发:允许你编写没有细微的Bug的代码,并在不引入新bug的情况下易于重构
使用线程同时运行代码
进程与线程
在大部分OS里,代码运行在进程(process)中,OS同时管理多个进程
在你的程序里,各独立部分可以同时运行,运行这些独立部分的就是线程
多线程运行:
提升性能表现
增加复杂性:无法保障各线程的执行顺序
多线程可导致的问题
竞争状态,线程以不一致的顺序访问数据或资源
死锁,两个线程彼此等待对方使用完所持有的资源,线程无法继续
只在某些情况下发生Bug,很难可靠的复制现象和修复
实现线程的方式
通过调用OS的API来创建线程:1:1模型
需要较小的运行时
语言自己实现的线程(绿色线程):M:N模型
需要更大的运行时
Rust:需要先权衡运行时的支持
Rust标准库仅提供1:1模型的线程
通过spawn创建新线程
通过thread::spawn函数可以创建新线程:
参数:一个闭包(在新线程里运行的代码)
use std::{ thread, time::Duration };
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}

通过join Handle来等待所有线程的完成
thread::spawn函数的返回所有值类型时JoinHandle
JoinHandle持有值得所有权
调用join方法,可以等待对应的其他线程的完成
join方法:调用handle的join方法会阻止当前运行线程的执行,直到handle所表示的这些线程终结

加入:
use std::{ thread, time::Duration };
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
使用Move闭包
move闭包通常和thread::spawn函数一起使用,它允许你使用其他线程的数据
创建线程时,把值的所有权从一个线程转移到另一个线程
use std::{ thread, time::Duration };
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}

使用消息传递来跨线程传递数据
消息传递
一种很流行且能保证安全并发的技术就是:消息传递
线程(或Actor)通过彼此发送消息(数据)来进行通信
Go语言的名言:不要用共享内存来通信,要用通信来共享内存
Rust:Channel(标准库提供)
Channel
Channel包含:发送端、接收端
调用发送端的方法,发送数据
接收端会检查和接收到达的数据
如果发送端、接收端中任意一段被丢弃,那么Channel就“关闭”了
创建Channel
使用mpsc::channel函数来创建Channel
mpsc来表示multiple producer、single consumer
返回一个tuple(元组):里面分别是发送端、接收端
(例子)
use std::{ thread, sync::mpsc };
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Cot: {}", received);
}

发送端的send方法
参数:想要发送的数据
返回: Result<T, E>
如果有问题(例如接收端已经被丢弃),就返回一个错误
接收端的方法
recv方法:阻止当前线程的执行,直到Channel中有值被送来
一旦有值收到,就返回Result<T, E>
当发送端关闭,就会收到一个错误
try_recv方法:不会阻塞,
立即返回Result<T, E>:
有数据达到:返回OK,里面包含着数据
否则,返回错误
通常会使用循环调用来检查try_recv的结果
Channel和所有权转移
所有权在消息传递中非常重要:能帮你编写安全、并发的代码
发送多个值,看到接受者在等待
use std::{ thread, sync::mpsc, time::Duration };
fn main() {
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_millis(1));
}
});
for received in rx {
println!("Cot: {}", received);
}
}

通过克隆创建多个发送者
use std::{ thread, sync::mpsc, time::Duration };
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx);
thread::spawn(move || {
let vals = vec![
String::from("1: hi"),
String::from("1: from"),
String::from("1: the"),
String::from("1: thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
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_millis(200));
}
});
for received in rx {
println!("Cot: {}", received);
}
}

共享状态的并发
使用共享来实现并发

使用Mutex来每次只允许一个线程来访问数据
Mutex时mutual exclusion(互斥锁)的简写
在同一时刻,Mutex只允许一个线程来访问某些数据
想要访问数据:
线程必须首先获取互斥锁(lock)
lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占访问权
Mutex通常被描述为:通过锁定系统来保护它所持有的的数据
Mutex的两条规则

Mutex<T>的API

use std::{ sync::Mutex };
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}

多线程共享Mutex<T>
例子报错
use std::{ sync::Mutex, thread };
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
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());
}

多线程的多重所有权
使用Arc<T>来进行原子引用计数
Arc<T>和Rc<T>类似,它可以用于并发情景
A:atomic,原子的
需要牺牲性能作为代价
Arc<T>和Rc<T>的API相同
use std::{ sync::{ Mutex, Arc }, thread };
fn main() {
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());
}


通过Send和Sync Trait来拓展并发
Send和Sync trait

Send:允许线程间转移所有权

Sync:允许多线程访问

手动实现Send和Sync是不安全的
总结
感觉学的东西越来越难了,不过我相信,坚持下去,总有顺手的一天。
三毛在散文《简单》里写道:“我避开无事时过分热络的友谊,这使我少些负担和承担。我不多说无谓的闲言,这使我觉得清畅。我当心的去爱别人,因为比较不会泛滥。我不求深刻,只求简单。”