两个线程操作std::deque,下述代码是否存在竞争条件?
为什么std::deque在多线程操作下会出现元素重复或丢失?
你遇到的问题本质是std::deque不是线程安全容器,所有成员函数都不支持并发读写操作,哪怕是看似“只读”的empty()或front(),在有其他线程修改容器时调用都会触发未定义行为。
具体竞争场景分析
你的代码里存在多个线程安全问题:
- 容器操作的竞态:当push线程执行
push_back()时,deque可能正在修改内部的存储结构(比如扩容、调整缓冲区指针),此时pop线程调用empty()、front()或pop_front(),会读取到不一致的内部状态,导致:- 读取
front()时拿到已经被弹出的元素(重复输出) pop_front()操作错误地跳过元素(丢失)- 甚至可能直接导致程序崩溃
- 读取
- 全局变量的无保护访问:
num、m、n、stop这些全局变量的读写都是非原子操作,比如m++实际包含“读-改-写”三步,多线程下会出现计数错误,进一步加剧行为异常。 - 检查-执行的窗口漏洞:哪怕是单pop线程,
if (!q.empty())和后续的q.front()/q.pop_front()之间不是原子操作,在这两个步骤之间,push线程可能还没完成push_back()的全部操作,或者极端情况下(比如push线程刚好在此时完成最后一个push并设置stop),都会导致逻辑错误。
平台差异的原因
MacOS使用的是libc实现的STL,Linux常用的是libstdc,两者对deque的内部实现(比如缓冲区管理、扩容时机)不同,libc++的实现可能更容易在这种无保护的并发场景下暴露竞态问题,所以你在Mac上更容易复现异常,而Linux上只是难复现,不是没有问题。
修复方案
要解决这个问题,必须给所有共享资源的访问加锁保护,示例修复代码如下:
#include <mutex> #include <condition_variable> #include <deque> #include <thread> #include <iostream> #include <cstdio> std::mutex q_mutex; std::condition_variable q_cv; int num = 10000; int m = 0; int n = 0; bool stop = false; std::deque<int> q; void push() { while(num--) { std::lock_guard<std::mutex> lock(q_mutex); q.push_back(m); printf("push succ: %d\n", m); m++; } { std::lock_guard<std::mutex> lock(q_mutex); stop = true; } q_cv.notify_one(); // 通知pop线程检查结束条件 std::cout << "m: " << m << std::endl; } void pop() { while(true) { std::unique_lock<std::mutex> lock(q_mutex); // 等待队列非空或收到停止信号 q_cv.wait(lock, []{ return !q.empty() || stop; }); if (stop && q.empty()) { break; } if (!q.empty()) { printf("front: %d\n", q.front()); q.pop_front(); n++; printf("n: %d\n", n); } lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(4)); } std::cout << "final n: " << n << std::endl; }
关键修复点:
- 用
std::mutex保护所有对q、m、n、stop、num的访问,确保同一时间只有一个线程操作这些共享资源 - 用
std::condition_variable替代空轮询,避免CPU空耗的同时,消除“检查-执行”的竞态窗口 - 所有涉及共享资源的操作都被严格限制在锁的保护范围内
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

