C++:条件变量等待
C++: condition-variable wait
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
std::mutex globalMutex;
std::condition_variable globalCondition;
int global = 0;
int activity = 0;
int CountOfThread = 1; // or more than 1
// just for console display, not effect the problem
std::mutex consoleMutex;
void producer() {
while (true) {
{
std::unique_lock<std::mutex> lock(globalMutex);
while (activity == 0) {
lock.unlock();
std::this_thread::yield();
lock.lock();
}
global++;
globalCondition.notify_one();
}
std::this_thread::yield();
}
}
void customer() {
while (true) {
int x;
{
std::unique_lock<std::mutex> lock(globalMutex);
activity++;
globalCondition.wait(lock); // <- problem
activity--;
x = global;
}
{
std::lock_guard<std::mutex> lock(consoleMutex);
std::cout << x << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int _tmain(int argc, _TCHAR* argv[])
{
for (int i = 0; i < CountOfThread; ++i) {
std::thread(customer).detach();
}
std::thread(producer).detach();
getchar();
return 0;
}
我想要的是确保每次有客户线程获得增加的全局值时,期望显示如下:1、2、3,...,但我看到的是全局值将增加wait and activity-- 因此,实际显示为:1, 23, 56, 78, ....
我发现问题出在 wait() 中,实际上 wait() 中有 3 个步骤,'unlock, wait, lock',在 signaled(wait return) 和 mutex.lock 之间,不是原子操作,producer线程可能在wait()锁mutex之前锁mutex,而activity仍然不为0,所以global会增加,没想到
有没有办法确定我的期望?
你的问题是,当activity
> 0 时,producer
可以循环获取锁,递增全局,并通知条件变量。 (通知不一定要有相应的服务员)。
您对 thread.yield
的重复调用有点危险 - 它们意味着您正在轮询,而不是等待。我认为解决方案是您需要 两个 条件变量。生产者等待一个直到被消费者通知,消费者等待另一个直到它被生产者通知。不过,我不太确定您是如何与多个消费者一起工作的。
我发现它可以帮助我处理线程上下文中的事情。例如,如果您是客户,您还在等什么呢?就你而言,我的意思是在线程的上下文中。当您这样想时,使用 monitors 进行编码就会变得非常简单。
现在,正如 Martin 所说,我认为重复调用 thread.yield
有点可怕。这可能会导致代码的可怕交错。
为了举例说明为什么您的代码不起作用,让我们快速浏览一下:
- 创建多个客户,抢到锁的增加
activity
。然后该线程由于调用 wait
而进入休眠状态。
- 另一个线程在初始客户线程调用
wait
后唤醒。这是因为 wait
解锁了传递给它的互斥体。
- 该线程获取
globalMutex
并增加 activity
。然后等待。
- 重复
CountOfThread
个线程,因为这完全有可能使用多线程代码。
- 最后,生产者线程运行,看到
activity == 0
,然后解锁。但是,它没有 notify_one
(即发出信号)。然后它屈服了。这可能会导致未定义和混乱的代码。
我的建议:
- 切勿在等待条件时调用
yield
。这会导致复杂且难以阅读的无效监控代码。
- 想想每个线程在等待什么条件。如果不同的代码段在不同的条件下等待,则创建不同的锁。一段共享代码只用一把锁
- 对不同的条件使用不同的条件变量在某些情况下。如果条件依赖于不同的数据,一定要使用不同的条件变量。
在您的情况下,解决方案并不像您想象的那么复杂。使用我最初的建议:在线程的上下文中思考。例如,当生产者线程为运行时,它想在客户还没有注意到global
已经改变时等待。当客户线程 运行、 它希望在生产者未更改时等待 global
。
这是您想要的行为示例:
mutex m;
condition_variable cv;
int global = 0, prev_global = 0;
void producer()
{
while (true)
{
unique_lock<mutex> lock(m);
while (prev_global != global)
{
cv.wait(lock);
}
prev_global = global++;
cv.notify_one();
lock.unlock();
}
}
void customer()
{
while (true)
{
unique_lock<mutex> lock(m);
while (prev_global == global)
{
cv.wait(lock);
}
prev_global = global;
cv.notify_one();
lock.unlock();
}
}
int main()
{
vector<thread> pool;
for (int i = 0; i < 5; ++i)
{
pool.push_back(thread (customer));
}
pool.push_back(thread (producer));
for (auto it = pool.begin(); it != pool.end(); ++it)
{
it->join();
}
return 0;
}
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
std::mutex globalMutex;
std::condition_variable globalCondition;
int global = 0;
int activity = 0;
int CountOfThread = 1; // or more than 1
// just for console display, not effect the problem
std::mutex consoleMutex;
void producer() {
while (true) {
{
std::unique_lock<std::mutex> lock(globalMutex);
while (activity == 0) {
lock.unlock();
std::this_thread::yield();
lock.lock();
}
global++;
globalCondition.notify_one();
}
std::this_thread::yield();
}
}
void customer() {
while (true) {
int x;
{
std::unique_lock<std::mutex> lock(globalMutex);
activity++;
globalCondition.wait(lock); // <- problem
activity--;
x = global;
}
{
std::lock_guard<std::mutex> lock(consoleMutex);
std::cout << x << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int _tmain(int argc, _TCHAR* argv[])
{
for (int i = 0; i < CountOfThread; ++i) {
std::thread(customer).detach();
}
std::thread(producer).detach();
getchar();
return 0;
}
我想要的是确保每次有客户线程获得增加的全局值时,期望显示如下:1、2、3,...,但我看到的是全局值将增加wait and activity-- 因此,实际显示为:1, 23, 56, 78, ....
我发现问题出在 wait() 中,实际上 wait() 中有 3 个步骤,'unlock, wait, lock',在 signaled(wait return) 和 mutex.lock 之间,不是原子操作,producer线程可能在wait()锁mutex之前锁mutex,而activity仍然不为0,所以global会增加,没想到
有没有办法确定我的期望?
你的问题是,当activity
> 0 时,producer
可以循环获取锁,递增全局,并通知条件变量。 (通知不一定要有相应的服务员)。
您对 thread.yield
的重复调用有点危险 - 它们意味着您正在轮询,而不是等待。我认为解决方案是您需要 两个 条件变量。生产者等待一个直到被消费者通知,消费者等待另一个直到它被生产者通知。不过,我不太确定您是如何与多个消费者一起工作的。
我发现它可以帮助我处理线程上下文中的事情。例如,如果您是客户,您还在等什么呢?就你而言,我的意思是在线程的上下文中。当您这样想时,使用 monitors 进行编码就会变得非常简单。
现在,正如 Martin 所说,我认为重复调用 thread.yield
有点可怕。这可能会导致代码的可怕交错。
为了举例说明为什么您的代码不起作用,让我们快速浏览一下:
- 创建多个客户,抢到锁的增加
activity
。然后该线程由于调用wait
而进入休眠状态。 - 另一个线程在初始客户线程调用
wait
后唤醒。这是因为wait
解锁了传递给它的互斥体。 - 该线程获取
globalMutex
并增加activity
。然后等待。 - 重复
CountOfThread
个线程,因为这完全有可能使用多线程代码。 - 最后,生产者线程运行,看到
activity == 0
,然后解锁。但是,它没有notify_one
(即发出信号)。然后它屈服了。这可能会导致未定义和混乱的代码。
我的建议:
- 切勿在等待条件时调用
yield
。这会导致复杂且难以阅读的无效监控代码。 - 想想每个线程在等待什么条件。如果不同的代码段在不同的条件下等待,则创建不同的锁。一段共享代码只用一把锁
- 对不同的条件使用不同的条件变量在某些情况下。如果条件依赖于不同的数据,一定要使用不同的条件变量。
在您的情况下,解决方案并不像您想象的那么复杂。使用我最初的建议:在线程的上下文中思考。例如,当生产者线程为运行时,它想在客户还没有注意到global
已经改变时等待。当客户线程 运行、 它希望在生产者未更改时等待 global
。
这是您想要的行为示例:
mutex m;
condition_variable cv;
int global = 0, prev_global = 0;
void producer()
{
while (true)
{
unique_lock<mutex> lock(m);
while (prev_global != global)
{
cv.wait(lock);
}
prev_global = global++;
cv.notify_one();
lock.unlock();
}
}
void customer()
{
while (true)
{
unique_lock<mutex> lock(m);
while (prev_global == global)
{
cv.wait(lock);
}
prev_global = global;
cv.notify_one();
lock.unlock();
}
}
int main()
{
vector<thread> pool;
for (int i = 0; i < 5; ++i)
{
pool.push_back(thread (customer));
}
pool.push_back(thread (producer));
for (auto it = pool.begin(); it != pool.end(); ++it)
{
it->join();
}
return 0;
}