多线程共享std::list日志队列时,读取pop操作是否需要加互斥锁?
结论
必须在popLog方法中加锁,否则必然存在线程安全隐患,和日志写入频率无关,低频率下未出问题只是没触发竞争场景,不代表逻辑正确。
问题原因
std::list本身不是线程安全容器,你的场景下存在多个线程并发修改同一个容器的行为:
- 写入线程调用
push_back会修改链表的尾节点指针、内部维护的长度计数等核心数据 - UI线程调用
size()/front()/pop_front会读取长度计数、修改头节点指针,同样属于容器修改操作
并发操作没有同步的情况下,会直接触发未定义行为,常见表现包括:日志内容乱码、程序直接崩溃、日志莫名丢失、明明有日志却读到空字符串等,写入频率越高,触发问题的概率越大。
修复后的代码
std::string popLog() { std::lock_guard<std::mutex> lk(m_mutex); // 推荐用empty()替代size() == 0,部分STL实现中empty()是O(1)且更高效 if (m_queLogs.empty()) { return ""; } // 用std::move避免字符串拷贝,提升性能 auto zLog = std::move(m_queLogs.front()); m_queLogs.pop_front(); return zLog; }
可选优化(减少锁竞争)
你可以改成单次加锁批量读取所有日志,减少UI线程加锁的频次,进一步降低对高频率写入线程的影响:
// Logger新增批量读取接口 std::vector<std::string> popAllLogs() { std::lock_guard<std::mutex> lk(m_mutex); std::vector<std::string> logs; logs.reserve(m_queLogs.size()); while (!m_queLogs.empty()) { logs.push_back(std::move(m_queLogs.front())); m_queLogs.pop_front(); } return logs; } // UI线程修改为批量追加 void CLogView::OnTimer(UINT_PTR nIDEvent) { auto logs = Logger::instance()->popAllLogs(); for (const auto& zLog : logs) { CString strLog = convert_utf8_to_cstring(zLog); m_ctrLogView.AppendString(strLog); } }
内容的提问来源于stack exchange,提问作者coding monster
相关产品推荐
相关产品推荐

