在条件变量中使用谓词的正确方法

Proper way to use predicate in conditional variable

我想知道是否需要在锁定的互斥范围内重置谓词布尔变量。现在,我有一个 std::unique_lock 和一个 lambda 函数作为谓词参数 - lambda 函数 returns 一个布尔标志。这是否意味着我需要在锁保护范围内将布尔标志设置回 false?

如果我重置布尔标志,它似乎工作得更快,但我不确定这是否是处理此问题的安全方法。

#include <thread>
#include <condition_variable>
#include <mutex>

std::condition_variable _conditional;
bool processed = false;
std::mutex _mutex;

void Worker() {
    while (true) {
        //Doing other things ..
        //
        {
            //mutating shared data
            std::unique_lock<std::mutex> _lock(_mutex);
            _conditional.wait(_lock, [] { return processed; });
            processed = false? //do I set this boolean back to false or just leave as is?
            data++;
        }
    }
}

void Reader() {
   //just prints out the changed data
    while (true) {
        std::cout << data << std::endl;
        processed = true;
        _conditional.notify_one();
    }

}

int main(){

   std::thread t1(Worked);
   std::thread t2(Reader);

   t1.join();
   t2.join();

}

首先,Reader 从不获取锁来同步它对共享数据的访问(在本例中是 processed 布尔值和 data 变量,无论它是什么) Worker。因此,Reader 可以修改 processedWorker 正在读取它,而 Reader 可以从 data 读取而 Worker正在写信给它;这些都是竞争条件。它们可以通过在修改 processed 或从 data 读取之前让 Reader 也锁定互斥锁来修复。此答案的其余部分假定已进行此更正。

其次,是否应该将 processed 重置回 false 取决于您希望应用程序执行的操作,因此有必要了解后果。

如果它永远不会重置为 false,那么 Worker 将永远不会再等待条件变量(尽管它会不断地重新获取互斥锁并检查 processed 的值,尽管事实上保证 在第一个等待终止后为真),并且它将简单地继续递增 data。即使您像我提到的那样正确地同步了对共享数据的访问,这仍然可能无法按照您的意愿进行。 Worker 很有可能在 Reader 之前连续多次获取互斥锁,因此 data 可以在打印之间多次递增, data 可以在增量之间打印多次(在这种情况下 没有 打印和增量的顺序保证)

如果在 Worker 内每次等待后将 processed 重置为 false,则可以保证 data 至少 打印 [=56] =] 在每次递增之间进行一次,因为它无法递增 data 直到 Reader 通知它(这至少需要先打印一次)。但是,它可能仍会在每个增量之间打印多次,因为仍然没有机制强制 Reader 等待 Worker.

如果您提供另一种机制允许 Reader 也等待 Worker,那么理论上您可以保证每次打印在每次增量(交替)之间恰好发生一次。但是一旦你走到这一步,整个过程就是 运行 连续的,所以真的没有必要再使用多线程了。

请注意,这些方法中的每一种都具有完全不同的语义。您应该选择哪一个取决于您希望您的应用程序做什么。