在条件变量中使用谓词的正确方法
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
可以修改 processed
而 Worker
正在读取它,而 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
,那么理论上您可以保证每次打印在每次增量(交替)之间恰好发生一次。但是一旦你走到这一步,整个过程就是 运行 连续的,所以真的没有必要再使用多线程了。
请注意,这些方法中的每一种都具有完全不同的语义。您应该选择哪一个取决于您希望您的应用程序做什么。
我想知道是否需要在锁定的互斥范围内重置谓词布尔变量。现在,我有一个 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
可以修改 processed
而 Worker
正在读取它,而 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
,那么理论上您可以保证每次打印在每次增量(交替)之间恰好发生一次。但是一旦你走到这一步,整个过程就是 运行 连续的,所以真的没有必要再使用多线程了。
请注意,这些方法中的每一种都具有完全不同的语义。您应该选择哪一个取决于您希望您的应用程序做什么。