据我所知,从lock_guard被删除到函数(在另一个线程中运行(实际返回之间有相当长的时间。请参阅TEST(…(中的以下注释
bool bDone = false;
void run_worker(Foo* f) {
f->Compute();
bDone = true;
}
TEST(FooTest,ThreadFoo) {
Foo* f = makeFoo();
std::thread worker( run_worker, f );
worker.detach();
micro_wait(100); // wait for N microseconds
f->Reset(); // should block until Compute() is done
// !!?? Why is this necessary !?!?
int k=0;
while(++k<500 && !bDone)
micro_wait(100);**
EXPECT_TRUE(bDone); // Fails even with a single micro_wait(100)!
}
什么时候/为什么会有这样的时间流逝,有什么好的解释吗介于f->Compute((完成和bDone设置之间?我怀疑互斥锁会被解锁,而清理Compute((中分配的基于堆栈的变量仍有工作要做,但这纯粹是一个假设。
计算和重置的存根如下:
void Foo::Compute() {
std::lock_guard<std::mutex> guard(m_Mutex);
// ... allocate bunch of temporary stuff on stack, update *this
}
void Foo::Reset() {
std::lock_guard<std::mutex> guard(m_Mutex);
// ... simpler stuff, clear
}
bDone
没有同步。
编译器很可能在bDone
的值为false时将其加载到寄存器中,然后继续使用寄存器缓存的版本,而不是从内存中获取更新的版本。或者,您的指令可能会被重新排序,以便在释放锁后bDone
被设置为false。
正确的方法是使用std::atomic<bool>
。工作线程可以通过对bDone.store(true)
的调用来更新它,而等待线程可以通过调用bDone.load()
来读取它的最新值。
如果您想了解内存排序以帮助理解为什么需要原子,您可以通过使用acquire
和release
排序来进一步改进这一点(尽管对于单元测试来说,这并不重要(。
除此之外,你真正应该做的是加入你的工人线程。连接会一直阻塞到线程结束,因此可以确保Compute
函数已完成执行。如果你担心它可能会永远运行(或运行太久(,我建议使用boost::thread
而不是std::thread
,因为它提供了一个timed_join
函数,在指定的时间段后停止等待线程。