crossbeam是好东西,简化了rust并发编程,支持无锁编程,对性能要求高的程序可以尝试。
crossbeam-AtomCell
线程安全的单值操作,和std的Cell相似,std的cell对并发不友好,AtomCell并发情况下也可保证内存安全
基本用法
use crossbeam::atomic::AtomicCell;
use crossbeam::scope;
fn main() {
let value = AtomicCell::new(0u8); //创建一个新的Cell,初始值为0,类型为u8
scope(|s| {
let thread1 = s.spawn(|_| {
for _ in 0..10 {
value.fetch_add(1); // +1 10次
}
});
let thread2 = s.spawn(|_| {
for _ in 0..5 {
value.fetch_sub(1); // -1 5次
}
});
thread1.join().unwrap();
thread2.join().unwrap();
})
.unwrap();
let final_value = value.load(); // load 获取cell的值
println!("Final value: {}", final_value);
}
//结果: Final value: 5
crossbeam-channel
比std::sync::mpsc更好的MPSC(多生产者、单消费者)队列,性能更好,功能更多、支持select!(友好易用)
基础示例
use crossbeam::channel::unbounded;
fn main(){
let (tx, rx) = unbounded(); // 新建一个无界channel
tx.send(1).unwrap(); // 发送数字1到channel
let recv: i32 = rx.recv().unwrap(); // 从channel中收取信息,如果没有信息则阻塞
println!("{}", recv);
}
// 1
无界channel和有界channel
bounded: 有界channel,声明时需要加上channel的容量,e.g. 声明一个容纳10个对象的channel:// let s, r = bounded(10);
unbounded: 无界channel,channel容量会根据数据自动调整,在编写系统级应用时个人建议采用有界channel(感觉某些情况下内存不受控)
有界channel的用法
声明一个有界channel
use crossbeam::channel::bounded;
fn main(){
let (tx, rx) = bounded(2);
tx.send(1).unwrap();
let recv: i32 = rx.recv().unwrap();
println!("{}", recv);
}
当channel容量已满时,继续插入数据会阻塞直到channel数据被接收
use crossbeam::channel::bounded;
fn main(){
let (tx, rx) = bounded(2);
tx.send(1).unwrap();
println!("{}",tx.len());
tx.send(2).unwrap();
println!("{}",tx.len());
tx.send(3).unwrap(); // 这里会阻塞,因为channel容量为2,当前channel已满,如果前面插入的数据未消费是无法继续插入数据的
println!("{}",tx.len());
let recv: i32 = rx.recv().unwrap();
println!("{}", recv);
}
channel在多线程复用时需要clone
下面是官方示例:
use crossbeam_channel::unbounded;
let (s1, r1) = unbounded();
let (s2, r2) = (s1.clone(), r1.clone());
let (s3, r3) = (s2.clone(), r2.clone());
s1.send(10).unwrap();
s2.send(20).unwrap();
s3.send(30).unwrap();
assert_eq!(r3.recv(), Ok(10));
assert_eq!(r1.recv(), Ok(20));
assert_eq!(r2.recv(), Ok(30));
多线程模式下的channel使用方法
感觉平常用不上,引用官方示例:
use crossbeam_channel::bounded;
use crossbeam_utils::thread::scope;
let (s, r) = bounded(0);
scope(|scope| {
// Spawn a thread that receives a message and then sends one.
scope.spawn(|_| {
r.recv().unwrap();
s.send(2).unwrap();
});
// Send a message and then receive one.
s.send(1).unwrap();
r.recv().unwrap();
}).unwrap();
关闭channel
use crossbeam_channel::{unbounded, RecvError};
let (s, r) = unbounded();
s.send(1).unwrap();
s.send(2).unwrap();
s.send(3).unwrap();
// 关闭channel的发送端
drop(s);
// 接收端还能接受剩余的数据
assert_eq!(r.recv(), Ok(1));
assert_eq!(r.recv(), Ok(2));
assert_eq!(r.recv(), Ok(3));
// 数据收完后channel就为空
assert!(r.is_empty());
// 这时候r.recv不会阻塞
// 会直接返回 `Err(RecvError)`
assert_eq!(r.recv(), Err(RecvError));
好用的Select
好用的东西,通常放在loop中,用来处理消息等非常方便
定义几个channel操作,那个操作有消息就走哪个那个逻辑,否则就走default或者一直阻塞
比如:
loop {
select
通道A => 处理A
通道B => 处理B
}
引用官方示例:
use std::thread;
use std::time::Duration;
use crossbeam_channel::{select, unbounded};
let (s1, r1) = unbounded(); // 创建两个无界channel
let (s2, r2) = unbounded();
thread::spawn(move || s1.send(10).unwrap()); // 异步发送消息到channel
thread::spawn(move || s2.send(20).unwrap());
// At most one of these two receive operations will be executed.
select! {
recv(r1) -> msg => assert_eq!(msg, Ok(10)), // 根据不同的channel收到数据做不同的处理
recv(r2) -> msg => assert_eq!(msg, Ok(20)),
default(Duration::from_secs(1)) => println!("timed out"), // 如果所有channel都没有收到数据就走default,1秒后打印time out
}
select的扩展方法
- after 创建一个多长时间以后会插入一个消息的channel(做延迟发送)
- tick 创建一个定时发送消息的channel(定时器)
- never 创建一个从不发送消息的channel
用法示例(还是引用官方示例,比较简单):
use std::time::{Duration, Instant};
use crossbeam_channel::{after, select, tick};
let start = Instant::now();
let ticker = tick(Duration::from_millis(50));
let timeout = after(Duration::from_secs(1));
loop {
select! {
recv(ticker) -> _ => println!("elapsed: {:?}", start.elapsed()),
recv(timeout) -> _ => break,
}
}