是我的等待 - 使用std :: mutex通知机制正确

Is my wait - notify mechanism using std::mutex correct?

本文关键字:mutex 通知 机制 std 我的 等待 使用      更新时间:2023-10-16

我开始使用std :: mutexes停止线程并等待另一个线程恢复它。它是这样的工作:

线程1

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());
// Locks it again to pause this thread
myWaitMutex.lock();

线程2

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

但是,我不确定这是否正确,并且在所有平台上都没有问题。如果这不是正确的,那么在C 11中实现此操作的正确方法是什么?

代码的问题

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());

.try_lock()尝试锁定锁并返回 true,如果成功,,该代码表示" 如果我们将锁定锁定,然后重试,一次又一次地锁定,直到我们失败。我们永远无法"失败",因为我们目前拥有自己正在等待的锁,因此这将是无限的循环。另外,尝试使用呼叫者已经锁定的std::mutex锁定是UB,因此可以保证这是UB。如果不成功,.try_lock()将返回false,并且while循环将退出。换句话说,这将不是确保静音将被锁定。

确保二线锁定的正确方法是简单的:

myWaitMutex.lock();

这将导致当前螺纹被阻止(无限期(,直到可以锁定锁。

接下来,另一个线程试图解锁mutex 有锁定。

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

这是不起作用的,因为它尚未锁定在std::mutex上的 .unlock()

使用锁

使用Mutex锁时,使用raii所有权 - 包装对象(例如std::lock_guard(更容易。std::mutex的使用模式始终是:" 锁 ->在关键部分中做某事 -> unlock "。std::lock_guard将互斥X锁定在其构造函数中,并将其解锁在其破坏者中。无需担心何时锁定和解锁以及如此低级的东西。

std::mutex m;
{
    std::lock_guard<std::mutex> lk{m};
    /* We have the lock until we exit scope. */
} // Here 'lk' is destroyed and will release lock.

一个简单的锁可能不是工作的最佳工具

如果您想要的是能够发出线程唤醒的信号,则使用等待并使用std::condition_variable通知结构。std::condition_variable允许任何呼叫者在不握住任何锁的情况下将信号发送到等待线程

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std::literals;
int main() {
    std::mutex m;
    std::condition_variable cond;
    std::thread t{[&] {
        std::cout << "Entering sleep..." << std::endl;
        std::unique_lock<std::mutex> lk{m};
        cond.wait(lk); // Will block until 'cond' is notified.
        std::cout << "Thread is awake!" << std::endl;
    }};
    std::this_thread::sleep_for(3s);
    cond.notify_all(); // Notify all waiting threads.
    t.join(); // Remember to join thread before exit.
}

但是,为了进一步使事情复杂化,有一个称为 spurious Wakeups 的东西,这意味着由于未知原因,任何等待线程都可能随时醒来。这是大多数系统上的事实,与线程调度的内部工作有关。另外,由于我们正在处理并发时,我们可能需要检查是否确实需要等待。例如,如果通知线程碰巧在我们开始等待之前通知,那么我们可能会永远等待,除非我们有办法先检查此。

要处理此操作,我们需要添加一个时循环和一个告诉我们何时需要等待以及何时等待的谓词。

int main() {
    std::mutex m;
    std::condition_variable cond;
    bool done = false; // Flag for indicating when done waiting.
    std::thread t{[&] {
        std::cout << "Entering sleep..." << std::endl;
        std::unique_lock<std::mutex> lk{m};
        while (!done) { // Wait inside loop to handle spurious wakeups etc.
            cond.wait(lk);
        }
        std::cout << "Thread is awake!" << std::endl;
    }};
    std::this_thread::sleep_for(3s);
    { // Aquire lock to avoid data race on 'done'.
        std::lock_guard<std::mutex> lk{m};
        done = true; // Set 'done' to true before notifying.
    }
    cond.notify_all();
    t.join();
}

还有其他原因是,在循环中等待并使用诸如@david Schwartz的评论中提到的"被盗唤醒"之类的谓词是一个好主意。

在我看来,您正在寻找条件变量。最后,应该总是有一种使其通过静音作用的方法,但是条件变量是当前的C 惯用方法来处理"块并等到发生某些情况"的情况。

当固定螺纹锁定的线程时,静音的行为是未定义的。当不固定的线程试图解锁的线程时,静音的行为是不确定的。因此,您的代码可能会在各个平台上完全执行任何操作。

相反,将静音与条件变量和谓词布尔一起使用。在伪代码中:

block:

  1. 获取静音。

  2. 虽然谓词是错误的,但在条件变量上阻止。

  3. 如果您想在此处重新安装武器,请将谓词设置为false。

  4. 释放静音。

发布:

  1. 获取静音。

  2. 将谓词设置为true。

  3. 信号条件变量。

  4. 释放静音。

to arenm:

  1. 获取静音。

  2. 将谓词设置为false。

  3. 释放静音。

请检查此代码....

std::mutex m_mutex;             
std::condition_variable m_cond_var;    
void threadOne(){
    std::unique_lock<std::mutex> lck(mtx);
    while (!ready){ 
        m_cond_var.wait(lck);
    }
    m_cond_var.notify_all();
}
void threadTwo(){
    std::unique_lock<std::mutex> lck(mtx);
    read = true;
    m_cond_var.notify_all();
}

希望您能得到解决方案。而且这是非常合适的!