Rust中Mutex和RwLock的使用

Rust中Mutex和RwLock都在sync包下面,

Rust标准库中的sync包提供了很多有用的线程同步原语:

  • Mutex/RwLock - 用来保护共享可变/只读数据,实现互斥/读写锁功能

  • mpsc::Sender/Receiver - 实现线程间的 channel 通讯

  • Arc/Rc - 引用计数指针,用于在线程间安全共享数据

  • Barrier - 同步多个线程到同一点进行等待

  • Condvar - 条件变量,配合 Mutex 实现等待/通知机制

  • Once - 确保只执行初始化操作一次

  • atomic - 常见的原子操作如 AtomicUsize、AtomicBool 等

  • spin - 带有轮询锁的 Mutex 实现

  • Semaphore - 信号量,控制并发数量

  • JoinHandle - 操作其他线程的句柄

  • scoped_threadpool - 可轻松构建工作窃取线程池

  • utils - 线程名称和线程安全标记方便工具

  • lazy_static - 实现延迟初始化的全局静态变量

  • futures、tokio - 异步编程模型

此外,还有一些高级同步原语:

  • crossbeam - channels、队列、锁等原语

  • parking_lot - parking_lot实现的互斥量、原子操作等

  • rayon - rust数据并行框架

rust标准库的sync包提供了很全面的线程安全支持,开发偏低并发的程序已经足够。高级并发场景需要使用更加复杂的同步原语。



在Rust中,以下一些数据结构是线程安全的:

  • Arc/Rc - 引用计数指针,可以在线程间安全地共享数据。

  • Mutex - 线程同步原语,允许同时只有一个线程访问锁定的数据。

  • RwLock - 读写锁,允许多个线程同时读,但写时只允许一个线程。

  • RefCell/Cell - 运行时检查借用规则,可以在不受静态借用检查限制的情况下进行内部可变性。

  • Atomic数据类型 - 如AtomicUsize,AtomicBool等,提供原子操作来访问值。

  • Channel - 用于线程间安全地发送和接收消息。

  • Barrier - 同步多个线程直至一个点。

  • Once - 确保某个初始化只执行一次。

  • spin::Mutex - spinlock实现的Mutex,适用于快速锁定解锁的情况。

  • Condvar - 条件变量,配合Mutex实现等待通知机制。

  • scoped_threadpool - 线程池,使用任务来共享工作负载。

这些数据结构通常利用互斥锁、原子操作等技术来保证线程安全访问数据。
不需要额外同步的还有像String、Vec等内存安全的类型。

所以在Rust中,使用这些内置线程安全数据结构可以方便高效地实现多线程程序的同步控制。




在Rust中,MutexRwLock都是用于管理并发访问共享数据的同步原语,但它们在用法和用途上有所不同。

Mutex(互斥锁)

  • 作用Mutex提供了对共享数据的互斥访问。一次只能有一个线程可以访问内部数据。如果另一个线程希望访问数据,它必须等待当前持有锁的线程释放锁。
  • 使用场景:当您需要确保一次只有一个线程可以修改共享数据时,使用Mutex是一个好选择。

如何使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::sync::{Arc, Mutex};
use std::thread;


fn main() {
println!("Hello, world!");
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());
}

执行cargo run,输出为:


RwLock(读写锁)

  • 作用RwLock允许多个线程同时读取共享数据,但写入数据时需要独占访问。这意味着如果没有线程写入数据,多个线程可以同时读取数据,但写入数据时,其他所有读写操作都会被阻塞,直到写操作完成。
  • 使用场景RwLock适用于读操作远多于写操作的场景,它通过允许多个线程并发读取来提高性能。

如何使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::sync::{Arc, RwLock};
use std::thread;

let data = Arc::new(RwLock::new(0));

// 写线程
let data_clone = Arc::clone(&data);
let write_thread = thread::spawn(move || {
let mut data = data_clone.write().unwrap();
*data += 1;
});

// 读线程
let data_clone = Arc::clone(&data);
let read_thread = thread::spawn(move || {
let data = data_clone.read().unwrap();
println!("The value is {}", *data);
});

write_thread.join().unwrap();
read_thread.join().unwrap();

区别

  • 并发读取Mutex不允许多个线程同时访问共享数据,即使所有线程都只是读取数据。而RwLock允许多个线程在没有写入操作时同时读取数据。
  • 性能:在读多写少的场景中,RwLock可以提供比Mutex更好的性能,因为它允许并发读取。
  • 用法复杂度:虽然RwLock在某些情况下性能更优,但也更复杂,因为您需要管理读写锁的状态,以避免如写饥饿(长时间等待写锁而不释放读锁)等问题。

选择Mutex还是RwLock取决于具体场景:如果共享数据的写操作不频繁,且希望最大化读操作的并发性,RwLock可能是更好的选择。如果共享数据经常被修改,或者您想要简化并发控制的复杂度,使用Mutex可能更合适。