C++ 条件等待竞争条件
C++ conditional wait race condition
假设我有一个程序,它有一个工作线程,它对队列中的数字求平方。问题是如果工作很轻(需要很短的时间来完成),worker 完成工作并在它有时间甚至有时间等待 worker 完成之前通知主线程。
我的简单程序如下所示:
#include <atomic>
#include <condition_variable>
#include <queue>
#include <thread>
std::atomic<bool> should_end;
std::condition_variable work_to_do;
std::mutex work_to_do_lock;
std::condition_variable fn_done;
std::mutex fn_done_lock;
std::mutex data_lock;
std::queue<int> work;
std::vector<int> result;
void worker() {
while(true) {
if(should_end) return;
data_lock.lock();
if(work.size() > 0) {
int front = work.front();
work.pop();
if (work.size() == 0){
fn_done.notify_one();
}
data_lock.unlock();
result.push_back(front * front);
} else {
data_lock.unlock();
// nothing to do, so we just wait
std::unique_lock<std::mutex> lck(work_to_do_lock);
work_to_do.wait(lck);
}
}
}
int main() {
should_end = false;
std::thread t(worker); // start worker
data_lock.lock();
const int N = 10;
for(int i = 0; i <= N; i++) {
work.push(i);
}
data_lock.unlock();
work_to_do.notify_one(); // notify the worker that there is work to do
//if the worker is quick, it signals done here already
std::unique_lock<std::mutex> lck(fn_done_lock);
fn_done.wait(lck);
for(auto elem : result) {
printf("result = %d \n", elem);
}
work_to_do.notify_one(); //notify the worker so we can shut it down
should_end = true;
t.join();
return 0;
}
你试图通过条件变量使用通知本身作为工作完成的标志,这从根本上是有缺陷的。首先也是最重要的是 std::conditional_variable
可能会有虚假的唤醒,所以不应该这样做。您应该使用队列大小作为工作结束的实际条件,在所有线程中保护的相同互斥锁下检查和修改它,并对条件变量使用相同的互斥锁。然后你可以使用 std::conditional_variable
等到工作完成,但是你在检查队列大小之后才这样做,如果工作已经完成,你根本就不会去等待。否则,您将在循环中检查队列大小(由于虚假唤醒)并等待它是否仍不为空,或者您将 std::condition_variable::wait()
与内部具有循环的谓词一起使用。
假设我有一个程序,它有一个工作线程,它对队列中的数字求平方。问题是如果工作很轻(需要很短的时间来完成),worker 完成工作并在它有时间甚至有时间等待 worker 完成之前通知主线程。
我的简单程序如下所示:
#include <atomic>
#include <condition_variable>
#include <queue>
#include <thread>
std::atomic<bool> should_end;
std::condition_variable work_to_do;
std::mutex work_to_do_lock;
std::condition_variable fn_done;
std::mutex fn_done_lock;
std::mutex data_lock;
std::queue<int> work;
std::vector<int> result;
void worker() {
while(true) {
if(should_end) return;
data_lock.lock();
if(work.size() > 0) {
int front = work.front();
work.pop();
if (work.size() == 0){
fn_done.notify_one();
}
data_lock.unlock();
result.push_back(front * front);
} else {
data_lock.unlock();
// nothing to do, so we just wait
std::unique_lock<std::mutex> lck(work_to_do_lock);
work_to_do.wait(lck);
}
}
}
int main() {
should_end = false;
std::thread t(worker); // start worker
data_lock.lock();
const int N = 10;
for(int i = 0; i <= N; i++) {
work.push(i);
}
data_lock.unlock();
work_to_do.notify_one(); // notify the worker that there is work to do
//if the worker is quick, it signals done here already
std::unique_lock<std::mutex> lck(fn_done_lock);
fn_done.wait(lck);
for(auto elem : result) {
printf("result = %d \n", elem);
}
work_to_do.notify_one(); //notify the worker so we can shut it down
should_end = true;
t.join();
return 0;
}
你试图通过条件变量使用通知本身作为工作完成的标志,这从根本上是有缺陷的。首先也是最重要的是 std::conditional_variable
可能会有虚假的唤醒,所以不应该这样做。您应该使用队列大小作为工作结束的实际条件,在所有线程中保护的相同互斥锁下检查和修改它,并对条件变量使用相同的互斥锁。然后你可以使用 std::conditional_variable
等到工作完成,但是你在检查队列大小之后才这样做,如果工作已经完成,你根本就不会去等待。否则,您将在循环中检查队列大小(由于虚假唤醒)并等待它是否仍不为空,或者您将 std::condition_variable::wait()
与内部具有循环的谓词一起使用。