std::unique_lock 和 std::condition_variable 是如何工作的
How do std::unique_lock and std::condition_variable work
我需要弄清楚锁定和 condition_variable 是如何工作的。
在此处的 - 稍微修改过的 - 代码中 cplusplusreference
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
std::this_thread::sleep_for(std::chrono::seconds(1));
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
让我感到困惑的是,如果 worker_thread 已经锁定了互斥体,主线程如何锁定它。
从this answer我看到是因为cv.wait
解锁了互斥体
但现在我对此感到困惑:如果 cv.wait
会解锁它,那么为什么我们还需要锁定它呢?
例如,我可以这样做吗?
std::unique_lock<std::mutex> lk(m, std::defer_lock);
因此,我创建了锁定对象,因为 cv 需要它,但我在创建时没有锁定它。
现在有什么不同吗?
我不明白为什么在那种情况下我会得到 "runtime error" here。
引自std::condition_variable::wait() :
Calling this function if lock.mutex() is not locked by the current thread is undefined behavior.
我认为你的误解源于对锁是什么以及它们如何与条件变量交互的更深层次的误解。
锁存在的基本原因是提供互斥。互斥保证代码的某些部分仅由单个线程执行。这就是为什么您不能等到以后再使用锁的原因——您需要锁定它才能获得保证。
当您希望执行代码的其他部分但在执行当前代码段时仍需要互斥时,这会导致问题。这就是条件变量派上用场的地方:它们提供了一种结构化的方式来释放锁,并保证当你再次醒来时你会把它拿回来。这就是为什么锁在等待函数中被解锁的原因。
我将尝试添加更多关于为什么条件变量需要锁的解释。
你必须有锁,因为你的代码需要检查条件谓词是否为真。谓词是必须为真才能继续的某个值或值的组合。它可以是一个指针,它是 NULL 或指向一个完整的数据结构可供使用。
您必须在等待之前锁定它并检查谓词,因为当您开始等待条件时,另一个线程可能已经设置了它。
条件通知和等待返回并不意味着条件为真。这仅意味着条件在某个时间为真。它甚至可能是真的,然后是假的,然后又是真的。这也可能意味着您的线程处于不相关的信号处理程序中,导致条件等待中断。你的代码甚至不知道条件通知被调用了多少次。
因此,一旦条件等待 returns,它就会锁定互斥锁。现在您的代码可以在锁中安全地检查条件。如果为真,则代码可以更新它需要更新的内容并释放锁。如果它不是真的,它就会回到等待重试的状态。例如,它可以获取该数据结构指针并将其复制到向量中,然后将受锁保护的指针设置回 NULL。
将条件视为提高轮询循环效率的一种方式。您的代码仍然需要在循环等待中完成它会做的所有事情 运行,除了它可以进入睡眠状态而不是不停地旋转。
我需要弄清楚锁定和 condition_variable 是如何工作的。
在此处的 - 稍微修改过的 - 代码中 cplusplusreference
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
std::this_thread::sleep_for(std::chrono::seconds(1));
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
让我感到困惑的是,如果 worker_thread 已经锁定了互斥体,主线程如何锁定它。
从this answer我看到是因为cv.wait
解锁了互斥体
但现在我对此感到困惑:如果 cv.wait
会解锁它,那么为什么我们还需要锁定它呢?
例如,我可以这样做吗?
std::unique_lock<std::mutex> lk(m, std::defer_lock);
因此,我创建了锁定对象,因为 cv 需要它,但我在创建时没有锁定它。
现在有什么不同吗?
我不明白为什么在那种情况下我会得到 "runtime error" here。
引自std::condition_variable::wait() :
Calling this function if lock.mutex() is not locked by the current thread is undefined behavior.
我认为你的误解源于对锁是什么以及它们如何与条件变量交互的更深层次的误解。
锁存在的基本原因是提供互斥。互斥保证代码的某些部分仅由单个线程执行。这就是为什么您不能等到以后再使用锁的原因——您需要锁定它才能获得保证。
当您希望执行代码的其他部分但在执行当前代码段时仍需要互斥时,这会导致问题。这就是条件变量派上用场的地方:它们提供了一种结构化的方式来释放锁,并保证当你再次醒来时你会把它拿回来。这就是为什么锁在等待函数中被解锁的原因。
我将尝试添加更多关于为什么条件变量需要锁的解释。
你必须有锁,因为你的代码需要检查条件谓词是否为真。谓词是必须为真才能继续的某个值或值的组合。它可以是一个指针,它是 NULL 或指向一个完整的数据结构可供使用。
您必须在等待之前锁定它并检查谓词,因为当您开始等待条件时,另一个线程可能已经设置了它。
条件通知和等待返回并不意味着条件为真。这仅意味着条件在某个时间为真。它甚至可能是真的,然后是假的,然后又是真的。这也可能意味着您的线程处于不相关的信号处理程序中,导致条件等待中断。你的代码甚至不知道条件通知被调用了多少次。
因此,一旦条件等待 returns,它就会锁定互斥锁。现在您的代码可以在锁中安全地检查条件。如果为真,则代码可以更新它需要更新的内容并释放锁。如果它不是真的,它就会回到等待重试的状态。例如,它可以获取该数据结构指针并将其复制到向量中,然后将受锁保护的指针设置回 NULL。
将条件视为提高轮询循环效率的一种方式。您的代码仍然需要在循环等待中完成它会做的所有事情 运行,除了它可以进入睡眠状态而不是不停地旋转。