微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在不解锁互斥锁的情况下通知 condtion_variable 仍然可以正常工作为什么?

如何解决在不解锁互斥锁的情况下通知 condtion_variable 仍然可以正常工作为什么?

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk,[]{return ready;});
 
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    //lk.unlock(); /// here!!!!!!!!!!!
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk,[]{return processed;});
    }
    std::cout << "Back in main(),data = " << data << '\n';
 
    worker.join();
}

我不知道为什么这段代码在worker_thread中的notify_one之前没有解锁就可以工作。
我想如果我在通知之前不解锁,唤醒主线程将再次阻塞,因为互斥锁仍然由 worker_thread 持有。
之后worker_thread将解锁互斥锁(因为unique_lock在销毁时解锁互斥锁)。
然后没人能唤醒沉睡的主线程。

但是这段代码在notify之前无需解锁mutex就可以很好地工作。
这是如何工作的???? (我看了 cppreference 的评论,但我看不懂)

解决方法

这里有两件事要谈。首先,它为什么有效,其次,为什么你不想先调用 unlock。

之所以有效,是因为 cv.wait(lk,[]{return processed;}); 在等待通知时实际上会解锁 lk

一些序列。 Main 先获得锁:

 MAIN                       WORKER
 auto lk = lock();          
                            auto lk = lock(); (blocks)
 cv.wait(lk,condition);
 checks condition; fails
 releases lk
                            wakes up with lk
                            fullfill condition
                            cv.notify_one();
 wakes up from notify
 tries to reget lk,blocks
                            lk.unlock();
 wakes up with lk.
 checks condition; passes

Worker 先获得锁:

 MAIN                       WORKER
                            auto lk = lock();
 auto lk = lock(); (blocks)         
                            fullfill condition
                            cv.notify_one();
                            lk.unlock();
 wakes up with lk.
 cv.wait(lk,condition);
 checks condition; passes

对于我们先解锁的情况:

 MAIN                       WORKER
 auto lk = lock();          
                            auto lk = lock(); (blocks)
 cv.wait(lk,condition);
 checks condition; fails
 releases lk
                            wakes up with lk
                            fullfill condition
                            lk.unlock();
                            cv.notify_one();
 wakes up from notify
 gets lk
 checks condition; passes

Worker 先获得锁,最后有两种可能:

 MAIN                       WORKER
                            auto lk = lock();
 auto lk = lock(); (blocks)         
                            fullfill condition
                            lk.unlock();
 wakes up with lk.
 cv.wait(lk,condition);
 checks condition; passes
                            cv.notify_one(); (nobody cares)

 MAIN                       WORKER
                            auto lk = lock();
 auto lk = lock(); (blocks)         
                            fullfill condition
                            lk.unlock();
                            cv.notify_one(); (nobody cares)
 wakes up with lk.
 cv.wait(lk,condition);
 checks condition; passes

现在,为什么持有锁更好?

因为 C++ 标准库的作者使它变得更好。库知道 cv waiter 与互斥锁相关联,并且知道当前线程持有它。

所以实际发生的是:

 MAIN                       WORKER
 auto lk = lock();          
                            auto lk = lock(); (blocks)
 cv.wait(lk,condition);
 checks condition; fails
 releases lk
                            wakes up with lk
                            fullfill condition
                            cv.notify_one(); // knows listener holds mutex,waits for
                            lk.unlock(); // and actually wakes up listening threads
 wakes up from notify
 gets lk
 checks condition; passes

现在有比上面更多的可能性。 condition_variable 具有“虚假”唤醒功能,即使没有人通知您,您也会被唤醒。所以在使用它时必须遵守纪律。

一般规则是双方必须共享一个互斥锁,并且必须在测试条件改变和通知之间的任何时刻保持锁定。最好的情况是,锁应该一直保持到通知发送后立即生效。

这除了防止条件状态本身的竞争条件之外。但是如果你使用原子条件状态,它避免了状态上的竞争条件,但它不满足条件变量的锁定要求。

上述规则——在该时间间隔内的某个时间(可能在整个时间间隔内)保持锁定——是一种简化,但足以保证您不会丢失通知。完整的规则意味着回到 C++ 的内存模型,并对你的代码所做的事情进行痛苦的证明,老实说,我不想再这样做了。所以我使用这个经验法则。

为了说明原子条件“状态”和无锁会出现什么问题;

 MAIN                       WORKER
 auto lk = lock();
 cv.wait(lk,condition);
 checks condition; fails
                            fullfill condition
                            cv.notify_one(); (nobody cares)
 goes to sleep,releases lk

再也没有发生任何事情。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。