删除std::lock_guard相对于其他堆栈分配对象的顺序/速度



据我所知,从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()来读取它的最新值。

如果您想了解内存排序以帮助理解为什么需要原子,您可以通过使用acquirerelease排序来进一步改进这一点(尽管对于单元测试来说,这并不重要(。

除此之外,你真正应该做的是加入你的工人线程。连接会一直阻塞到线程结束,因此可以确保Compute函数已完成执行。如果你担心它可能会永远运行(或运行太久(,我建议使用boost::thread而不是std::thread,因为它提供了一个timed_join函数,在指定的时间段后停止等待线程。

最新更新