Rust 中 Mutex、RwLock 的使用实例
在多线程编程中,同步访问共享数据是一个常见的需求。Rust 标准库的 std::sync::Mutex
提供互斥锁。
导入依赖
首先,我们需要导入标准库中的 std::sync::Mutex
:
1use std::sync::{Mutex, Arc};
Arc
也是必要的,因为在多线程环境下,需要使用 Arc
来安全地共享 Mutex
。
创建 Mutex
1let mutex = Mutex::new(0);
这里创建了一个 Mutex
包裹的可变整数,并初始化为 0。
使用 Mutex
获取锁并修改数据
要访问被 Mutex
保护的数据,首先需要获取锁。可以使用 lock
方法来获取锁,并在作用域内处理数据:
1{
2 let mut data = mutex.lock().unwrap();
3 *data += 1;
4} // 在这里锁会被自动释放
在这个例子中,lock
方法返回一个 MutexGuard
,它是一个 RAII 结构,在作用域结束时会自动释放锁。通过解引用操作符 *
可以获取被保护数据的可变引用,然后进行操作。
使用 Arc 包装 Mutex
如果要在多个线程中共享 Mutex
,则需要使用 Arc
来保证安全性。Arc
是原子引用计数的智能指针,可以安全地跨线程共享所有权。
1let mutex = Arc::new(Mutex::new(0));
2let mut handles = vec![];
3
4for _ in 0..10 {
5 let mutex = Arc::clone(&mutex);
6 let handle = std::thread::spawn(move || {
7 let mut data = mutex.lock().unwrap();
8 *data += 1;
9 });
10 handles.push(handle);
11}
12
13for handle in handles {
14 handle.join().unwrap();
15}
在这个例子中,Arc::clone
会增加引用计数,并在每个线程中传递 Arc
的所有权,确保 Mutex
在不同线程中的共享。
使用 read
方法
如果只需要读取 Mutex
中的数据,可以使用 read
方法获取读锁,这样多个线程可以同时读取数据,而不会阻塞。
1let data = mutex.read().unwrap();
2println!("Data: {}", *data);
错误处理
在获取锁时,需要处理可能的错误。lock
方法返回一个 Result
类型,如果获取锁失败,会返回 Err
。
1let data = mutex.lock();
2if let Ok(mut data) = data {
3 *data += 1;
4} else {
5 println!("Failed to acquire lock!");
6}
综合示例
1use std::collections::HashMap;
2use std::fs;
3use std::fs::File;
4use std::io::{self, Read, Write};
5use std::sync::{Arc, Mutex, RwLock};
6
7struct FileManager {
8 locks: Mutex<HashMap<String, Arc<RwLock<()>>>>,
9}
10
11impl FileManager {
12 fn new() -> Self {
13 FileManager {
14 locks: Mutex::new(HashMap::new()),
15 }
16 }
17
18 fn read_file(&self, file_path: &str) -> io::Result<Vec<u8>> {
19 let mut buf = Vec::new();
20 {
21 let lock_ref: Arc<RwLock<()>>= {
22 let mut locks = self.locks.lock().unwrap();
23 Arc::clone(&locks.entry(file_path.to_string()).or_default())
24 };
25 let _read_guard = lock_ref.read().unwrap();
26
27 let mut file = File::open(file_path)?;
28 file.read_to_end(&mut buf)?;
29 }
30 Ok(buf)
31 }
32
33 fn write_file(&self, file_path: &str, data: &[u8]) -> io::Result<()> {
34 let lock_ref = {
35 let mut locks = self.locks.lock().unwrap();
36 Arc::clone(locks.entry(file_path.to_string()).or_default())
37 };
38 let _write_guard = lock_ref.write().unwrap();
39
40 let mut file = File::create(file_path)?;
41 file.write_all(data)?;
42 Ok(())
43 }
44}
45
46use std::thread;
47fn main() {
48 let manager = Arc::new(FileManager::new());
49 let file_path = "example.txt";
50
51 let mut handles = vec![];
52
53 for i in 0..10 {
54 let manager_clone = Arc::clone(&manager);
55 let path = file_path.to_string();
56 handles.push(thread::spawn(move || {
57 manager_clone.write_file(&path, format!("Hello from thread {}!\n", i).as_bytes()).expect("Write failed");
58 }));
59 }
60
61 for _ in 0..10 {
62 let manager_clone = Arc::clone(&manager);
63 let path = file_path.to_string();
64 handles.push(thread::spawn(move || {
65 let data = manager_clone.read_file(&path).expect("Read failed");
66 println!("Read from thread: {}", String::from_utf8_lossy(&data));
67 }));
68 }
69
70 for handle in handles {
71 handle.join().unwrap();
72 }
73}
这个例子中,FileManager
类使用了 Rust 的标准库中的 Mutex
和 RwLock
来实现对文件的并发读写控制。
使用的方法
-
Mutex:
-
Mutex::new()
: 初始化一个新的互斥锁。 -
lock()
: 对互斥锁进行加锁,返回一个包含锁定资源的MutexGuard
。 -
unwrap()
: 在MutexGuard
上调用,用于处理可能的PoisonError
(当持有锁的线程崩溃时发生)。
-
-
RwLock
-
read()
: 获取读锁,返回一个RwLockReadGuard
,允许多个线程同时读取数据但不允许写入。 -
write()
: 获取写锁,返回一个RwLockWriteGuard
,只允许一个线程写入数据并阻止其他读写操作。
-
使用技巧
-
锁的层次化管理
FileManager
使用一个Mutex<HashMap<String, Arc<RwLock<()>>>>
来管理对各个文件的访问。这里的HashMap
映射了文件路径到一个RwLock
的Arc
(原子引用计数指针)。这种设计允许对每个文件独立地进行读写锁控制。
-
避免死锁
- 在
read_file
和write_file
方法中,首先锁定整个HashMap
,然后只在需要时获取对应文件的RwLock
。这样的设计减少了持锁时间,尽快释放Mutex
锁,从而减少死锁的可能性。
- 在
-
资源共享与优化
- 使用
Arc
允许多个线程共享对RwLock
的访问权,而RwLock
又允许多个线程同时读取同一个资源,只在写入时限制为单线程操作。这种设计有效地提高了并发性能。
- 使用
另外,注意看 let _read_guard = lock_ref.read().unwrap();
这行代码能够获得一个读锁,后面的语句可以在锁的保护下运行。正常情况我们是基于 lock_ref.read()
所包含的数据来操作,因此不用声明一个”未使用“变量,但这行代码,我们知识利用了读锁,并不直接用读锁内的数据。