Rust中如何提前终止多个等待RwLock::write锁的线程任务
解决方案
核心思路
标准库的RwLock没有提供主动终止其他线程写锁等待的API,因此不需要尝试从锁层面终止等待,而是从逻辑上避免多余线程进入写锁等待队列,从根源解决问题。
方案1:原子标记实现单线程扩容(推荐)
新增两个原子状态变量控制扩容流程,确保同一时间最多只有1个线程会尝试申请写锁执行扩容,其余线程直接等待扩容完成即可,完全不需要触碰写锁:
共享状态定义
use std::sync::{RwLock, atomic::{AtomicBool, AtomicUsize}}; struct SharedStore { memmap: RwLock<Mmap>, // 现有共享memmap is_expanding: AtomicBool, // 标记是否有线程正在执行扩容 memmap_version: AtomicUsize, // 每次扩容后版本号+1,用于判断扩容是否完成 capacity: usize, // 当前存储容量 }
Worker线程逻辑调整
fn thread_worker(tasks: Vec<Task>, shared_store: &SharedStore) { let mut read_lock = shared_store.memmap.read().unwrap(); let mut current_version = shared_store.memmap_version.load(Ordering::Acquire); for task in tasks { // 忽略空间检查的竞态条件,和原有逻辑一致 if out_of_space(&read_lock, &task) { // 先释放读锁 drop(read_lock); // CAS尝试抢占扩容权限,只有成功的线程会执行扩容 if shared_store.is_expanding.compare_exchange( false, true, Ordering::AcqRel, Ordering::Acquire ).is_ok() { // 抢到扩容权限,申请写锁执行扩容 let mut write_lock = shared_store.memmap.write().unwrap(); // 双重检查,避免已经被其他线程扩容过 if shared_store.memmap_version.load(Ordering::Acquire) == current_version { // 执行扩容逻辑:销毁旧memmap、重新分配文件、创建新memmap *write_lock = expand_mmap(write_lock.as_ref()); shared_store.capacity = new_capacity; // 版本号+1,通知其他线程扩容完成 shared_store.memmap_version.fetch_add(1, Ordering::Release); } // 释放写锁 drop(write_lock); // 标记扩容结束 shared_store.is_expanding.store(false, Ordering::Release); } else { // 没抢到扩容权限,等待扩容完成即可,不需要申请写锁 while shared_store.memmap_version.load(Ordering::Acquire) == current_version { std::thread::yield_now(); // 也可以用条件变量等待,减少空转开销 } } // 扩容完成,重新申请读锁继续处理 read_lock = shared_store.memmap.read().unwrap(); current_version = shared_store.memmap_version.load(Ordering::Acquire); } // 原有任务处理逻辑,直接操作read_lock对应的memmap process_task(&read_lock, task); } drop(read_lock); }
该方案完全避免了多线程同时等待写锁的问题,仅会有一个线程进入写锁等待队列,不会出现你提到的写锁排队导致的读锁阻塞问题。
方案2:兼容现有逻辑的最小修改
如果不想新增原子状态,也可以在现有逻辑基础上增加扩容完成标记,避免无效等待:
- 新增原子
AtomicBool类型的expand_finished,每次触发扩容前先设为false - 第一个拿到写锁的线程完成扩容后将
expand_finished设为true - 其余线程拿到写锁的第一时间先检查
expand_finished,如果为true直接释放写锁即可
该方案虽然还是会有多个线程进入写锁等待队列,但每个排队的线程拿到锁后会立即释放,不会造成长时间阻塞,仅产生可忽略的锁调度开销。
注意事项
如果使用的是写优先的RwLock实现(包括std默认实现、parking_lot的默认RwLock),只要有写锁处于等待队列,新的读锁申请就会被阻塞,因此方案1是最优选择,从根源避免多余写锁排队。
内容的提问来源于stack exchange,提问作者Yuri Astrakhan
相关产品推荐
相关产品推荐

