# 多线程状态变量修饰不加锁导致异常
多线程状态变量修饰不加锁导致异常
最近在重构一个SDK遇到一个问题:动态库中启动推送图片流,前端图像区展示一会卡住了,之后接口一直pending,之后控制台也卡住(本来会频繁打一些其他日志), 甚至有时候会发现动态库中使用的单例对象析构了😭, 简直离谱。 对应的两个线程函数简化后大致如下:
std::mutex mutex_;
std::condition_variable condVar;
std::atomic<bool> dataReady{false};
void processImage() {
std::unique_lock<std::mutex> lck(mutex_);
lck.unlock();
while (true) {
lck.lock();
while (!dataReady) {
condVar.wait(lck);
}
std::cout << "Waiting " << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(80));
lck.unlock();
}
std::cout << "Running " << std::endl;
}
void produceImage() {
std::unique_lock<std::mutex> lck(mutex_);
lck.unlock();
while (true) {
dataReady = false; // 应该先加锁后修改
lck.lock();
std::cout << "Data prepared" << std::endl;
dataReady = true;
std::this_thread::sleep_for(std::chrono::milliseconds(90));
lck.unlock();
condVar.notify_all();
}
}
执行启动推流接口后就开始启动两个线程分别执行这俩函数(实际不是永真循环),后来知乎上看了篇帖子, 状态变量即使是原子变量, 也需要上锁再修改, 否则还是可能出现死锁(我大概率已经遇到了). 接下来分析下为何会死锁:
- processImage:陷入睡眠, 释放了锁
- produceImage: 获取锁后生成图像, 接着释放锁并通知阻塞的的线程, processImage刚被唤醒后获取锁,接着再检查下dataReady,但这个时候可能produceImage执行完第25行,此时processImage又陷入沉睡, 如此一直循环往复。
实际测试这个现象不是必现的, C ++中一次直接测试都没遇到, java中调用倒是遇到的概率比较高。但我们可以在wait唤醒后再等待几微秒,让produceImage把状态变量修改为false(此时锁还在processImage线程手里,因此produceImage暂时不会继续执行), 在上面程序第11行的wait改为:
while (!dataReady) {
condVar.wait(lck);
std::this_thread::sleep_for(std::chrono::microseconds(5));
}
运行结果如下:

结果验证了我们的猜想,知道问题了修改就很简单了,只需要produceImage对dataReady修改前加锁。以上问题为何导致接口pending呢? 猜测是启动推流正常返回, 但stop时一直阻塞的线程无法检查结束标志位, join时主线程阻塞了。
但问题来了:线程阻塞为何会导致动态库析构呢?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)