如何让线程等待含内部线程的对象完成析构?
嘿,我来帮你搞定这个gTest里的线程时序问题!咱们一步步拆解:
你写的 DummyClass Obj(); 根本不是创建DummyClass对象,而是声明了一个名叫Obj、返回值为DummyClass的函数!这就导致你后续调用Obj.Func1(...)的逻辑完全不符合预期(说不定你的编译器没报错是因为有其他同名函数?)。正确的对象创建方式应该是:
DummyClass Obj; // 无参构造创建对象 // 或者C++11及以上用 DummyClass Obj{};
这是很多C++开发者都会踩的坑,先把这个修正了,咱们再解决核心的线程等待问题。
你现在用usleep是靠碰运气的延迟,完全不可靠——不同环境下线程执行速度不一样,延迟加少了还是会出问题,加多了又浪费时间。咱们用显式的生命周期控制+正确的线程停止逻辑来替代:
1. 确保SasThreadHelper的线程能正确响应停止信号
你的StopThread方法里设置了m_stop_sas_thread_flag然后调用join(),但关键是ReadMessagesFromQueue函数必须周期性检查这个停止标志,一旦标志置位就立刻退出。举个例子,如果你的线程是处理队列消息,应该这么写:
void SasThreadHelper::ReadMessagesFromQueue() { while (!m_stop_sas_thread_flag) { // 如果是阻塞式读取队列,要确保能被唤醒检查停止标志 std::unique_lock<std::mutex> lock(m_queue_mutex); // 等待队列有消息,或者收到停止信号 m_cv.wait(lock, [this](){ return !m_message_queue.empty() || m_stop_sas_thread_flag; }); // 收到停止信号就直接退出 if (m_stop_sas_thread_flag) break; // 处理队列消息的逻辑 auto msg = m_message_queue.front(); m_message_queue.pop(); lock.unlock(); // 执行消息处理(比如发请求给service) } }
另外,m_stop_sas_thread_flag必须是线程安全的!多个线程读写这个变量时,要用std::atomic<int>来声明,避免内存可见性问题:
std::atomic<int> m_stop_sas_thread_flag = 0;
如果你的线程函数里没检查这个标志,哪怕调用了StopThread,线程也会继续跑,甚至join()会一直阻塞,这肯定会导致时序混乱。
2. 显式控制DummyClass的生命周期,让它先于service停止
你当前的测试代码里,DummyClass对象(如果正确创建的话)是局部变量,它的析构会在service->Stop()之后(函数结束时才会析构局部变量),这和你想要的「service等对象析构完再停止」完全相反!
咱们用作用域大括号把对象包裹起来,让它在service->Stop()之前就完成析构:
TEST_F(TestFixture, TestName) { service->start(); // 启动service的线程 { // 把DummyClass对象放在这个作用域里 DummyClass Obj; EXPECT_CALL(*_Mock, Func(NotNull(), NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) .Times(1) .WillRepeatedly(Return(1)); EXPECT_EQ(true, Obj.Func1(a,b, c, d).IsOk()); } // 到这里,Obj会被析构!SasThreadHelper的析构函数会调用StopThread,等待线程完全退出 // 现在DummyClass的线程已经彻底退出了,再停止service service->Stop(); }
这样一来,不用usleep也能保证时序:service只会在DummyClass的线程完全结束后才停止,自然不会出现Connection refused(错误码111)的问题——这个错误本质就是service已经停了,但DummyClass的线程还在尝试连接/发请求。
错误码111的根源就是时序混乱:service先停了,DummyClass的线程还在跑,尝试连接已经关闭的服务端口。按照上面的步骤修正后,这个问题应该就能解决了。
内容的提问来源于stack exchange,提问作者Mohit Chandak

