有几个关于 SO 处理原子的问题,以及其他处理 std::condition_variable 的问题。但是我的问题是我在下面的使用是否正确?
三个线程,一个 ctrl 线程在取消暂停其他两个线程之前进行准备工作。 ctrl 线程还能够在工作线程(发送方/接收方)处于紧密的发送/接收循环中时暂停它们。使用原子的想法是在未设置暂停布尔值的情况下使紧密循环更快。
class SomeClass
{
public:
//...
// Disregard that data is public...
std::condition_variable cv; // UDP threads will wait on this cv until allowed
// to run by ctrl thread.
std::mutex cv_m;
std::atomic<bool> pause_test_threads;
};
void do_pause_test_threads(SomeClass *someclass)
{
if (!someclass->pause_test_threads)
{
// Even though we use an atomic, mutex must be held during
// modification. See documentation of condition variable
// notify_all/wait. Mutex does not need to be held for the actual
// notify call.
std::lock_guard<std::mutex> lk(someclass->cv_m);
someclass->pause_test_threads = true;
}
}
void unpause_test_threads(SomeClass *someclass)
{
if (someclass->pause_test_threads)
{
{
// Even though we use an atomic, mutex must be held during
// modification. See documentation of condition variable
// notify_all/wait. Mutex does not need to be held for the actual
// notify call.
std::lock_guard<std::mutex> lk(someclass->cv_m);
someclass->pause_test_threads = false;
}
someclass->cv.notify_all(); // Allow send/receive threads to run.
}
}
void wait_to_start(SomeClass *someclass)
{
std::unique_lock<std::mutex> lk(someclass->cv_m); // RAII, no need for unlock.
auto not_paused = [someclass](){return someclass->pause_test_threads == false;};
someclass->cv.wait(lk, not_paused);
}
void ctrl_thread(SomeClass *someclass)
{
// Do startup work
// ...
unpause_test_threads(someclass);
for (;;)
{
// ... check for end-program etc, if so, break;
if (lost ctrl connection to other endpoint)
{
pause_test_threads();
}
else
{
unpause_test_threads();
}
sleep(SLEEP_INTERVAL);
}
unpause_test_threads(someclass);
}
void sender_thread(SomeClass *someclass)
{
wait_to_start(someclass);
...
for (;;)
{
// ... check for end-program etc, if so, break;
if (someclass->pause_test_threads) wait_to_start(someclass);
...
}
}
void receiver_thread(SomeClass *someclass)
{
wait_to_start(someclass);
...
for (;;)
{
// ... check for end-program etc, if so, break;
if (someclass->pause_test_threads) wait_to_start(someclass);
...
}
原文由 Erik Alapää 发布,翻译遵循 CC BY-SA 4.0 许可协议
我查看了您操作条件变量和原子的代码,它似乎是正确的并且不会引起问题。
为什么即使它是原子的,你也应该保护对共享变量的写入:
如果在谓词中检查共享变量和等待条件之间发生写入共享变量,则可能会出现问题。考虑以下:
等待线程 虚假唤醒,获取互斥体,检查谓词并将其评估为
false
,因此它必须再次等待 cv。控制线程 将共享变量设置为
true
。控制线程 发送通知,任何人都没有收到,因为没有线程在等待条件变量。
等待线程 等待条件变量。由于通知已经发送,它会等到下一次虚假唤醒,或者控制线程下一次发送通知时。可能无限期地等待。
从没有锁定的共享原子变量中读取通常是安全的,除非它引入了 TOCTOU 问题。
在您的情况下,您正在读取共享变量以避免不必要的锁定,然后在锁定后再次检查它(在有条件的
wait
调用中)。这是一个有效的优化,称为双重检查锁定,我在这里看不到任何潜在的问题。您可能想检查
atomic<bool>
是否无锁。否则,如果没有它,您将拥有更多的锁。