C++中其他线程删除元素时,如何安全遍历std::list?
解决方案:多线程遍历并修改std::list的线程安全实现
你的核心问题是在避免全局遍历锁的前提下,解决std::list并发读(遍历)和写(删除)的线程安全问题——原代码中直接并发操作会导致迭代器失效、未定义行为,甚至段错误,因为std::list本身不是线程安全容器,并发读写不存在内置同步。
下面是几种可行的实现方案,兼顾性能和线程安全:
方案1:细粒度互斥锁+提前获取下一个迭代器
利用std::list的特性:删除元素只会让被删元素的迭代器失效,其他迭代器仍有效。通过缩小锁的范围(仅在获取元素、迭代器时加锁),避免全局遍历锁带来的性能损失。
代码实现
#include <mutex> #include <list> #include <iostream> #include <thread> std::list<Type> items; std::mutex mtx; void* Thread1(void*) { while(true) { auto it = items.begin(); while (it != items.end()) { // 仅在获取元素副本和下一个迭代器时加锁 std::lock_guard<std::mutex> lock(mtx); if (it == items.end()) break; // 防止刚拿到迭代器就被删到end Type item_copy = *it; auto next_it = std::next(it); // 提前保存下一个迭代器,避免当前it被erase后失效 lock.unlock(); // 无锁处理元素 std::cout << item_copy << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 重新加锁更新迭代器 std::lock_guard<std::mutex> lock2(mtx); it = next_it; } } } void* Thread2(void*) { // 删除操作全程加锁,确保原子性 std::lock_guard<std::mutex> lock(mtx); for(auto item = items.begin(); item != items.end(); ++item) { if(/* 你的条件判断 */) { items.erase(item); break; } } }
优缺点
- 优点:实现简单,无需修改元素结构,删除操作立即生效,锁粒度极小(仅毫秒级的同步操作),对遍历性能影响可以忽略。
- 缺点:Thread2删除时需要等待Thread1当前的锁释放(但Thread1的锁持有时间极短),极端高并发场景下可能有微小的延迟。
方案2:原子标记+延迟删除
如果对删除的时效性要求不高,可以给元素添加一个待删除的原子标记,Thread1遍历到标记元素时跳过,同时在遍历间隙清理已标记的元素。这种方式读操作几乎无锁,性能最优。
代码实现
#include <atomic> #include <mutex> #include <list> #include <iostream> #include <thread> struct Type { // 你的原有成员 std::atomic<bool> to_be_deleted{false}; // 原子标记,线程安全修改 }; std::list<Type> items; std::mutex mtx; void* Thread1(void*) { while(true) { auto it = items.begin(); while (it != items.end()) { // 无锁检查标记,未标记则处理元素 if (!it->to_be_deleted.load(std::memory_order_acquire)) { std::cout << *it << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } // 加锁清理已标记的元素 std::lock_guard<std::mutex> lock(mtx); if (it != items.end() && it->to_be_deleted.load(std::memory_order_acquire)) { it = items.erase(it); // erase后自动更新迭代器 } else { ++it; } } } } void* Thread2(void*) { std::lock_guard<std::mutex> lock(mtx); for(auto item = items.begin(); item != items.end(); ++item) { if(/* 你的条件判断 */) { item->to_be_deleted.store(true, std::memory_order_release); break; } } }
优缺点
- 优点:读操作完全无锁,遍历性能几乎不受影响,适合超大列表的高频遍历场景。
- 缺点:元素不会被立即删除,需等到Thread1遍历到该元素时才会清理;需要修改元素类型添加原子标记。
方案3:读写锁(std::shared_mutex)
如果未来可能扩展为多个读线程,可使用C++17引入的std::shared_mutex,允许多个读线程同时获取共享锁,写线程独占锁,进一步优化并发性能。
代码实现
#include <shared_mutex> #include <list> #include <iostream> #include <thread> std::list<Type> items; std::shared_mutex rw_mtx; void* Thread1(void*) { while(true) { auto it = items.begin(); while (it != items.end()) { // 读操作加共享锁 std::shared_lock<std::shared_mutex> lock(rw_mtx); if (it == items.end()) break; Type item_copy = *it; auto next_it = std::next(it); lock.unlock(); std::cout << item_copy << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::shared_lock<std::shared_mutex> lock2(rw_mtx); it = next_it; } } } void* Thread2(void*) { // 写操作加独占锁 std::unique_lock<std::shared_mutex> lock(rw_mtx); for(auto item = items.begin(); item != items.end(); ++item) { if(/* 你的条件判断 */) { items.erase(item); break; } } }
优缺点
- 优点:支持多读单写的高并发场景,锁粒度同样极小,性能表现优异。
- 缺点:依赖C++17及以上标准,实现复杂度略高于普通互斥锁。
内容的提问来源于stack exchange,提问作者DarKreter
相关产品推荐
相关产品推荐

