rust语言学习笔记(指针九)Arc<Mutex<T>>(多线程间共享可变性)

9.1 多线程共享计数器/状态

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let mut handles = Vec::new(); // 存储线程句柄的向量
    let counter = Arc::new(Mutex::new(0)); // 计数器,初始值为0

    for _ in 0..100 {
        let count = Arc::clone(&counter); // 克隆计数器,创建一个新的引用计数器
        handles.push(thread::spawn(move || {
            let mut c = count.lock().unwrap(); // 获取计数器的互斥锁,确保线程安全
            *c += 1; // 增加计数器的值
            println!("目前计数为:{}", *c);
        }));
    }
    
    for handle in handles {
        handle.join().unwrap(); // 等待线程完成执行
    }
}

9.2 异步任务间共享状态 (Tokio/Async)

rust 复制代码
use std::sync::Arc;
use tokio::sync::Mutex;

#[tokio::main]
async fn main() {
    let mut handles = Vec::new();            // 存储异步任务的句柄
    let counter = Arc::new(Mutex::new(0));   // 计数器,初始值为0

    for _ in 0..100 {
        let count = Arc::clone(&counter);    // 克隆计数器,创建一个新的引用计数器
        
        handles.push(tokio::spawn(async move {
            // 启动异步任务
            let mut c = count.lock().await;  // 获取计数器的互斥锁,确保线程安全
            *c += 1;                         // 增加计数器的值
            println!("目前计数为:{}", *c);
            
            // 模拟耗时操作(持有锁期间尽量避免,此处仅演示)
            tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        }));
    }
    
    for handle in handles {
        handle.await.unwrap();      // 等待异步任务完成执行
    }
}

9.3 后台线程与主线程通信

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread::{self, spawn};
use std::time;

fn main() {
    let mut handles = Vec::new();                    // 存储线程任务的句柄
    let datalist = Arc::new(Mutex::new(Vec::new())); // 数据列表,初始值为空
    for i in 0..10 {                                 // 启动10个线程
        let data = Arc::clone(&datalist);            // 克隆数据列表,每个线程都有自己的数据副本
        handles.push(spawn(move || {
            let mut d = data.lock().unwrap();        // 获取数据列表的可变锁,用于修改数据
            d.push(format!("{}线程的消息!", i));      // 向数据列表中添加线程的消息
            thread::sleep(time::Duration::from_millis(100));   // 模拟线程执行时间
        }));
    }
    for handle in handles {
        handle.join().unwrap();                                // 等待所有线程完成执行
    }
    println!("线程传来的数据:{:?}", datalist.lock().unwrap());  // 打印所有线程的消息
}

9.4 单例模式或全局配置

rust 复制代码
use std::sync::{Arc, Mutex, OnceLock};
use std::thread;
use std::time;

#[derive(Debug)]
struct Config {                                    // 应用配置结构体
    app_name: String,
    app_version: u16,
}
impl Config {
    fn load() -> Self {                             // 加载应用配置
        println!("正在从文件或环境变量中,加载配置...");
        Config {
            app_name: "MyApp".to_string(),
            app_version: 1,
        }
    }
}
static APP_CONFIG: OnceLock<Arc<Mutex<Config>>> = OnceLock::new();  // 应用配置单例

fn get_config() -> Arc<Mutex<Config>> {                             // 获取应用配置
    APP_CONFIG
        .get_or_init(|| {                       // 初始化应用配置
            let config = Config::load();        // 加载应用配置
            Arc::new(Mutex::new(config))        // 返回应用配置的Arc<Mutex<Config>>
        })
        .clone()                                // 返回应用配置的Arc<Mutex<Config>>
}

fn main() {
    let mut handles = Vec::new();              // 存储线程任务的句柄

    for i in 0..10 {                           // 启动10个线程
        let config = get_config();             // 克隆应用配置的Arc<Mutex<Config>>
        handles.push(thread::spawn(move || {
            {
                let mut cfg = config.lock().unwrap();   // 获取应用配置的可变锁,用于修改应用配置
                cfg.app_version += 1;                   // 应用配置版本号增加
            }
            thread::sleep(time::Duration::from_millis(100));  // 模拟线程执行时间
        }));
    }
    for handle in handles {
        handle.join().unwrap();                        // 等待所有线程完成执行
    }
    println!("当前配置信息:{:?}", *get_config().lock().unwrap());
}

9.5 关键注意事项

  1. 死锁风险 ‌:
    • 如果在持有锁期间调用其他可能再次获取同一锁的代码,会导致死锁。
    • 避免‌:尽量保持锁的粒度小,不要在持锁时执行复杂逻辑或 I/O。
  2. 性能瓶颈 ‌:
    • Mutex 是串行化的瓶颈。如果写操作非常频繁,线程会大量时间花在等待锁上。
    • 优化 ‌:
      • 如果‌读多写少 ‌,考虑使用 Arc<RwLock<T>>RwLock 允许并发读取,仅在写入时独占。
      • 如果数据是简单的原子类型(如计数器),考虑使用 Arc<AtomicUsize>,无锁且更快。
  3. 异步环境陷阱 ‌:
    • async 函数中,‌严禁 ‌使用 std::sync::Mutex::lock(),因为它会阻塞整个异步运行时的工作线程。
    • 正确做法 ‌:使用 tokio::sync::Mutexstd::sync::Mutex 配合 tokio::task::spawn_blocking
  4. 克隆开销 ‌:
    • Arc::clone() 只是增加引用计数,开销极小(原子操作),可以放心在循环中克隆。