【问题标题】:Proper way to use predicate in conditional variable在条件变量中使用谓词的正确方法
【发布时间】:2021-01-08 20:14:43
【问题描述】:

我想知道是否需要在锁定的互斥锁范围内重置谓词布尔变量。现在,我有一个带有 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();

}

【问题讨论】:

  • 这是设计此设置的人的问题。布尔标志的用途是什么?什么应该导致它被设置为true?什么应该导致它被设置为false?或者也许:false 值代表什么? true 值代表什么?
  • @JaMiT true 表示我刚刚完成了对共享数据的读取,并且Worker() 线程现在能够对其进行变异,从而用.notify_one() 通知工作线程它可以现在继续锁定和更改共享数据。我想我困惑的地方是我什至不知道我是否需要将其改回false?因为它仍然会运行(更快)而无需将其更改回false。这是否意味着工作线程甚至不会等待,因为无论是否调用.notify_one(),该标志始终为真?
  • 如果你引入了太多的含义,你很可能会把自己弄糊涂。尝试 "true 表示我刚刚完成了对共享数据的读取,并且 Worker() 线程现在能够对其进行变异" 句号。 不要'当标志为true 时,不了解发生的情况;看看那个值代表什么true 值表示 data 已被读取并准备好进行处理。简短,甜蜜,并且可以被否定:false 值意味着 data 尚未被读取或 data 未被处理。当_lock 超出范围时,以下哪一项成立?

标签: c++ multithreading lambda predicate condition-variable


【解决方案1】:

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

其次,processed 是否应该重置为 false 取决于您希望应用程序做什么,因此有必要了解后果。

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

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

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

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

【讨论】:

  • 很明显,如果您不希望您的应用程序做任何这些事情,您可能应该想出一个不同的、合适的解决方案。
  • 感谢您提供所有设计含义的答案。这正是我想知道的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-10
  • 1970-01-01
  • 2015-01-06
  • 1970-01-01
  • 2012-08-28
  • 2011-01-30
  • 2022-01-23
相关资源
最近更新 更多