条件变量、引用和线程:谁拥有锁?

1 c++ concurrency condition-variable c++11

假设我有一个类 ThreadQueue 持有一个std::queue,并且我将它的一个实例传递std::ref给一个线程。进一步假设,线程 1(主线程)创建并持有 ThreadQueue 对象并将消息倒入其中,第二个线程的任务是接收这些消息并将它们放在某处,例如,将它们写入日志文件。

该类看起来像:

#include <queue>
#include <mutex>
#include <condition_variable>

using namespace std;

template <typename T>
class ThreadQueue
{
    queue<T> q_;
    mutex mtx;
    unique_lock<mutex> lck;
    condition_variable cv;

public:
    ThreadQueue() { lck = unique_lock<mutex>(mtx); }
    ~ThreadQueue() { if (lck.owns_lock()) lck.unlock(); }

    void enqueue (const T&);
    T dequeue ();
};

template <typename T>
void ThreadQueue<T>::enqueue (const T& t)
{
    lck.lock();
    q_.push(t);
    lck.unlock();
    cv.notify_one();
}

template <typename T>
T ThreadQueue<T>::dequeue ()
{
    cv.wait(lck);
    lck.lock();
    T t = q_.front(); // let's assume that's a copy assignment, because
    q_.pop();         // pop() calls the descructor.
    lck.unlock();
    return t;
}
Run Code Online (Sandbox Code Playgroud)

然后主要的曲调是:

ThreadQueue<std::pair<int, std::string>> logs;
// and maybe something like:
std::thread logger(std::ref(logs));
Run Code Online (Sandbox Code Playgroud)

关键的一行是cv.wait(lck);文档明确指出lck需要是一个 unique_lock 对象,其互斥对象当前被该线程锁定。

现在的问题是:谁真正锁定了互斥锁,谁拥有锁,线程 1 还是线程 2?

Max*_*kin 5

代码中有两个主要错误:

  1. unique_lock不应该是成员变量。它必须在堆栈上创建,以便在离开作用域时(正常返回或异常时)为您自动释放锁。
  2. cv.wait只有在您检查队列确实为空后才能调用。std::condition_variable是一种无状态的通信机制,如果在没有服务员时发出信号,信号就会丢失。也有虚假唤醒。您可能喜欢使用cv.wait([this] { return !q_.empty(); });它为您正确处理条件变量的等待。

例如:

using namespace std;

template <typename T>
class ThreadQueue
{
    queue<T> q_;
    mutex mtx;
    condition_variable cv;

public:
    void enqueue (const T&);
    T dequeue ();
};

template <typename T>
void ThreadQueue<T>::enqueue (const T& t)
{
    {
        lock_guard<mutex> lck(mtx);
        q_.push(t);
    }
    cv.notify_one(); // Optimization: release the lock before signalling.
}

template <typename T>
T ThreadQueue<T>::dequeue ()
{
    unique_lock<mutex> lck(mtx);
    cv.wait(lck, [this] { return !q_.empty(); });
    T t = q_.front();
    q_.pop();
    return t;
}
Run Code Online (Sandbox Code Playgroud)

谁拥有锁?

锁定互斥锁的线程拥有锁,或进入临界区。双方std::lock_guardstd::unique_lock在这里锁定在析构函数的构造和解锁互斥(在正常范围内退出或例外)。