13

我在理解条件变量及其与互斥锁的使用方面遇到了一些麻烦,我希望社区可以帮助我。请注意,我来自 win32 背景,所以我与 CRITICAL_SECTION、HANDLE、SetEvent、WaitForMultipleObject 等一起使用。

这是我第一次尝试使用 c++11 标准库进行并发,它是此处找到的程序示例的修改版本。

#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;
}

关于这个的几个问题。

我读过“任何打算等待 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() 调用不会阻塞,而只是检查条件并继续?

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

4

2 回答 2

12

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

是的。

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

当您等待时,condition_variable它会解锁您通过它的锁,所以在

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

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

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

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

我希望另一个线程被饿死,因为互斥锁的解锁时间不足以让另一个线程锁定它。

我是否正确假设 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<bool>

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

于 2012-11-14T00:53:55.113 回答
2

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

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

std::atomic<bool>仅将 a用于退出标志更有意义(并且更清晰) 。然后你只需将它初始化为 false,在退出线程中设置为 true,然后在其他线程中检查它。

于 2012-11-14T00:53:20.637 回答