我在理解条件变量及其与互斥锁的使用方面遇到了一些麻烦,我希望社区可以帮助我。请注意,我来自win32背景,因此我使用CRITICAL_SECTION,HANDLE,SetEvent,WaitForMultipleObject等。
这是我第一次使用c ++ 11标准库进行并发尝试,它是program example found here的修改版本。
#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
std::queue<unsigned int> nNumbers;
std::mutex mtxQueue;
std::condition_variable cvQueue;
bool m_bQueueLocked = false;
std::mutex mtxQuit;
std::condition_variable cvQuit;
bool m_bQuit = false;
std::thread thrQuit(
[&]()
{
using namespace std;
this_thread::sleep_for(chrono::seconds(7));
// set event by setting the bool variable to true
// then notifying via the condition variable
m_bQuit = true;
cvQuit.notify_all();
}
);
std::thread thrProducer(
[&]()
{
using namespace std;
int nNum = 0;
unique_lock<mutex> lock( mtxQuit );
while( ( ! m_bQuit ) &&
( cvQuit.wait_for( lock, chrono::milliseconds(10) ) == cv_status::timeout ) )
{
nNum ++;
unique_lock<mutex> qLock(mtxQueue);
cout << "Produced: " << nNum << "\n";
nNumbers.push( nNum );
}
}
);
std::thread thrConsumer(
[&]()
{
using namespace std;
unique_lock<mutex> lock( mtxQuit );
while( ( ! m_bQuit ) &&
( cvQuit.wait_for( lock, chrono::milliseconds(10) ) == cv_status::timeout ) )
{
unique_lock<mutex> qLock(mtxQueue);
if( nNumbers.size() > 0 )
{
cout << "Consumed: " << nNumbers.front() << "\n";
nNumbers.pop();
}
}
}
);
thrQuit.join();
thrProducer.join();
thrConsumer.join();
return 0;
}
有关此问题的几个问题。
I've read that“任何打算在std :: condition_variable上等待的线程必须先获取std :: unique_lock。”
所以我有一个{quit mutex,condition variable&amp; bool}表示退出已发出信号。生产者和消费者线程必须各自获取std :: unique_lock,如下所示:
std::unique_lock<std::mutex> lock(m_mtxQuit);
这让我感到困惑。这不会锁定第一个线程中的退出互斥锁,从而阻塞第二个线程吗?如果这是真的,那么第一个线程如何释放锁定,以便另一个线程可以开始?
另一个问题:如果我将wait_for()调用更改为等待零秒,则该线程将被饿死。谁能解释一下?我希望在执行while循环之前不要阻塞(我是否正确地假设no_timeout被重新调用而不是超时?)。
如何调用wait_for()并指定零时间,以便wait_for()调用不会阻塞,而只是检查条件并继续?
我也有兴趣听到关于这个主题的好的参考资料。
答案 0 :(得分:12)
这不会锁定第一个线程中的退出互斥锁,从而阻塞第二个吗?
是
如果这是真的,那么第一个线程如何释放锁定,以便另一个线程可以开始?
当您在condition_variable
上等待时,它会解锁您传递的锁定,所以在
cvQuit.wait_for( lock, chrono::milliseconds(10) )
条件变量将调用lock.unlock()
然后阻塞最多10ms(这是原子地发生的,所以在解锁互斥锁和阻止条件可以准备好的地方之间没有窗口,你会错过它)
当互斥锁解锁时,它允许另一个线程获取锁定。
另一个问题:如果我将wait_for()调用更改为等待零秒,则该线程将被饿死。有人可以解释一下吗?
我希望其他线程被饿死,因为互斥锁没有解锁足够长的时间让其他线程锁定它。
我是否正确假设no_timeout被收回而不是超时?
不,如果持续时间过去而条件没有准备就绪,那么即使在零秒后它也会“超时”。
如何调用wait_for()并指定零时间,以便wait_for()调用不会阻塞,而只是检查条件并继续?
不要使用条件变量!如果您不想等待条件成为真,请不要等待条件变量!只需测试m_bQuit
然后继续。
(除此之外,为什么你的布尔调用m_bXxx
?他们不是成员,所以m_
前缀是误导性的,b
前缀看起来像匈牙利符号的那种可怕的MS习惯。哪个很臭。)
我也有兴趣听到关于这个主题的好的参考资料。
最好的参考是Anthony Williams的C++ Concurrency In Action,它详细介绍了整个C ++ 11原子和线程库,以及多线程编程的一般原则。关于这个主题的我最喜欢的书之一是Butenhof的Programming with POSIX Threads,它专门针对Pthreads,但C ++ 11工具与Pthreads非常接近,因此很容易将该书中的信息传递给C ++ 11多线程。
N.B。在thrQuit
中,您在不使用互斥锁保护它的情况下写入m_bQuit
,因为没有什么能阻止另一个线程在读取的同时读取它,这是一种竞争条件,即未定义的行为。对bool的写入必须由互斥锁保护,或者必须是原子类型,例如, std::atomic<bool>
我认为你不需要两个互斥锁,它只是增加了争用。由于你永远不会释放mtxQuit
,除非在等待condition_variable
时没有第二个互斥锁,mtxQuit
已经确保只有一个线程可以一次进入临界区。< / p>
答案 1 :(得分:1)
如果你想检查某些内容并继续,无论它是否真实(可能做两件事),那么条件变量是错误的。条件变量是一个低级原语,用于与您想要等待的锁定数据结构相关联的某些条件,而无需旋转获取和释放锁定。规范示例是队列 - 您有一个锁定访问队列的锁和两个条件变量(队列不为空且队列未满)。要在队列上推送一些东西,你获得锁定,检查它是否已满,等待未满的condvar(如果是),按下队列上的值,发出非空的condvar信号(因为它不再为空)和释放锁。弹出操作类似。
所以在你的情况下,你有一个不能满的简单队列,所以你需要一个锁和一个condvar。有一个完美的感觉。但是你有一个'退出'标志,你希望触发完成。你不想等待戒烟标志被设置 - 你想要实际工作直到它被设置 - 所以condvar在这里真的没有意义。是的,您可能会提出一个令人费解的安排,使其有效,但这会让人感到困惑,因为它没有使用条件变量作为条件变量。
使用std::atomic<bool>
作为退出标志更有意义(也更清晰)。然后你只需将它初始化为false,在你的退出线程中设置为true,并在其他线程中检查它。