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

问题描述

我想知道是否需要在锁定的互斥锁范围内重置谓词布尔变量。现在,我有一个带有 lambda 函数作为谓词参数的 std::unique_lock - lambda 函数返回一个布尔标志。这是否意味着我需要在锁定保护的范围内将布尔标志设置回 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 变量,无论它是什么)与 { {1}}。因此,Worker 可以在 Reader 读取时修改 processed,而 Worker 可以从 Reader 读取而 data正在写信给它;这些都是竞争条件。它们可以通过在修改 Worker 或从 Reader 读取之前让 processed 也锁定互斥锁来修复。此答案的其余部分假设已进行此更正。

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

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

如果您在 data 内的每次等待后将 processed 重置回 false,那么您可以保证 Worker至少打印一次-在每次增量之间,因为在 data 通知它之前它无法增加 data(这需要至少先打印一次)。但是,它可能仍会在每个增量之间多次打印,因为仍然没有强制 Reader 等待 Reader 的机制。

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

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