# 多线程状态变量修饰不加锁导致异常

多线程状态变量修饰不加锁导致异常

最近在重构一个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时主线程阻塞了。

但问题来了:线程阻塞为何会导致动态库析构呢?

posted @ 2023-06-25 00:12  shmilyt  阅读(13)  评论(0编辑  收藏  举报