【问题标题】:Using std::mutex, std::condition_variable and std::unique_lock使用 std::mutex、std::condition_variable 和 std::unique_lock
【发布时间】:2012-11-02 11:43:30
【问题描述】:

我在理解条件变量及其在互斥锁中的使用方面遇到了一些麻烦,我希望社区可以帮助我。请注意,我来自 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 & bool} 来指示何时发出退出信号。生产者和消费者线程必须各自获得一个 std::unique_lock :

std::unique_lock<std::mutex> lock(m_mtxQuit);

这让我很困惑。这不会锁定第一个线程中的退出互斥锁,从而阻塞第二个线程吗?如果这是真的,那么第一个线程如何释放锁,以便另一个线程可以开始?

另一个问题:如果我将 wait_for() 调用更改为等待零秒,则该线程将被饿死。有人可以解释吗?我希望它在执行 while 循环之前不会阻塞(我是否正确假设 no_timeout 是 recv'd 而不是超时?)。

如何调用 wait_for() 并指定零时间,以便 wait_for() 调用不会阻塞,而只是检查条件并继续?

我也很想听听关于这个主题的好的参考资料。

【问题讨论】:

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


    【解决方案1】:

    如果你想检查某事并继续不管它是否真实(可能做两件不同的事情),那么使用条件变量是错误的。条件变量是与锁定数据结构相关的某些条件的低级原语,您希望等待而不必旋转获取和释放锁。典型的例子是队列——你有一个锁保护对队列的访问和两个条件变量(队列不为空,队列不满)。要在队列中推送某些内容,您需要获取锁,检查它是否未满,等待未满的 condvar(如果是),将值推送到队列中,向非空 condvar 发出信号(因为它不再为空)和释放锁。 pop操作类似。

    因此,在您的情况下,您有一个不能满的简单队列,因此您需要一个锁和一个 condvar。完全有道理。但是,您有一个“退出”标志,您希望触发完成。你不想等待退出标志被设置——你想实际工作直到它被设置——所以 condvar 在这里真的没有意义。是的,你可以想出一个复杂的安排来使它工作,但这会令人困惑,因为它不使用条件变量作为条件变量。

    只使用std::atomic&lt;bool&gt; 作为退出标志更有意义(也更清晰)。然后你只需将它初始化为 false,在你的退出线程中设置为 true,然后在其他线程中检查它。

    【讨论】:

      【解决方案2】:

      这不会将退出互斥锁锁定在第一个线程中,从而阻塞第二个线程吗?

      是的。

      如果是这样,那么第一个线程如何释放锁,以便另一个线程可以开始?

      当您等待 condition_variable 时,它会解锁您传递给它的锁,所以在

      cvQuit.wait_for( lock, chrono::milliseconds(10) )
      

      条件变量将调用lock.unlock(),然后阻塞长达 10 毫秒(这是原子发生的,因此在解锁互斥锁和阻塞条件可能准备好的位置之间没有窗口,您会错过它)

      当互斥锁被解锁时,它允许其他线程获取它的锁。

      另一个问题:如果我将 wait_for() 调用更改为等待零秒,则该线程将被饿死。谁能解释一下?

      我预计 other 线程会被饿死,因为互斥锁的解锁时间不足以让其他线程锁定它。

      我是否正确假设 no_timeout 是 recv'd 而不是超时?

      不,如果持续时间过去了条件还没有准备好,那么即使在零秒后它也会“超时”。

      如何调用 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 多线程。

      注意在thrQuit 中,您写入m_bQuit 而不用互斥锁保护它,因为没有什么能阻止另一个线程在写入的同时读取它,这是一个竞争条件,即未定义的行为。对 bool 的写入必须受互斥体保护,或者必须是原子类型,例如std::atomic&lt;bool&gt;

      我认为您不需要两个互斥锁,它只会增加争用。由于您从不释放mtxQuit,除非在等待condition_variable 时,拥有第二个互斥锁没有意义,mtxQuit 已经确保只有一个线程可以一次进入临界区。

      【讨论】:

      • 谢谢你,这真的解决了我的问题。我正在看 Anthony Williams 的书,但想知道是否还有更多。很好的答案,谢谢!
      猜你喜欢
      • 2013-12-29
      • 1970-01-01
      • 2012-10-17
      • 1970-01-01
      • 2016-09-07
      • 2020-11-15
      • 1970-01-01
      • 1970-01-01
      • 2017-03-28
      相关资源
      最近更新 更多