使用std :: mutex,std :: condition_variable和std :: unique_lock

时间:2012-11-14 00:03:15

标签: c++ multithreading concurrency mutex condition-variable

我在理解条件变量及其与互斥锁的使用方面遇到了一些麻烦,我希望社区可以帮助我。请注意,我来自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()调用不会阻塞,而只是检查条件并继续?

我也有兴趣听到关于这个主题的好的参考资料。

2 个答案:

答案 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,并在其他线程中检查它。