【问题标题】:Is std::condition_variable::notify_one reentrant?std::condition_variable::notify_one 是可重入的吗?
【发布时间】:2018-09-20 11:23:20
【问题描述】:

我可以安全地执行以下代码吗?这里是否可能出现死锁或某些意外行为,尤其是当 SIGINT 到达时?

#include <atomic>
#include <condition_variable>
#include <csignal>

std::mutex m;
std::condition_variable cv;
std::atomic<bool> flag(false);

void f1(){
             std::signal(SIGTERM, [](int signal){flag.store(true, 
             std::memory_order_release); cv.notify_one();});//register for signal
             std::unique_lock<std::mutex> mtx(m);
             cv.wait(mtx, []{return flag.load(std::memory_order_consume);});//wait for signal or f2() notify
         }

void f2(){
             flag.store(true, std::memory_order_release);
             cv.notify_one();
         }

int main(){
    std::thread th1(f1);
    std::thread th2(f2);
    th1.join();
    th2.join();
    return 0;
}

【问题讨论】:

    标签: c++ signals atomic condition-variable sigint


    【解决方案1】:

    pthread 函数和 std::condition_variablestd::mutex 使用它们是异步信号安全的。请参阅man signal-safety(7) 中的异步信号安全函数列表。


    有点题外话:如果您在更新flag 时没有锁定互斥锁,那么这会导致错过通知。想象一下场景:

    Thread 1        | Thread 2
                    | mutex.lock()
                    | flag == false
    flag = true     | 
    cv.notify_one() | <--- notification is lost
                    | cv.wait()
    

    这是一个非常常见的编程错误。

    更新flag 时锁定互斥锁可解决此问题。


    如果您想在收到信号时通知条件变量,请创建一个专用于信号处理的线程。示例:

    #include <condition_variable>
    #include <iostream>
    #include <thread>
    #include <signal.h>
    
    std::mutex m;
    std::condition_variable cv;
    bool flag = false;
    
    void f1(){
        std::unique_lock<std::mutex> lock(m);
        while(!flag)
            cv.wait(lock);
    }
    
    void signal_thread() {
        sigset_t sigset;
        sigfillset(&sigset);
        int signo = ::sigwaitinfo(&sigset, nullptr);
        if(-1 == signo)
            std::abort();
        std::cout << "Received signal " << signo << '\n';
    
        m.lock();
        flag = true;
        m.unlock();
        cv.notify_one();
    }
    
    int main(){
        sigset_t sigset;
        sigfillset(&sigset);
        ::pthread_sigmask(SIG_BLOCK, &sigset, nullptr);
    
        std::thread th1(f1);
        std::thread th2(signal_thread);
    
        th1.join();
        th2.join();
    }
    

    请注意,信号必须在所有线程中被阻塞,以便只有信号处理线程接收这些信号。

    【讨论】:

    • 通知丢失的好点,但不是根据 wait(lock, predicate()) - 如果 predicate() 在开始时返回 true(此处为 flag==true),它会立即返回。 (en.cppreference.com/w/cpp/thread/condition_variable/wait) 你如何看待信号屏蔽?在这种情况下,看起来在 f2() 中调用 std::raise(SIGTERM) 而不是 cv.notify() 就足够了。
    • @MOJNICK wait(lock, predicate()) 为您循环 while(!predicate()) cv.wait(lock);。当predicate 更新并在该检查之后但在cv.wait(lock) 之前发出通知时,通知将丢失。不幸的是,wait(lock, predicate()) 没有魔法。
    • 感谢您对两个问题的回答:1. while(!flag) cv.wait(lock);cv.wait(lock, []{return flag;}); 完全相同吗? 2. 我可以在signal_thread() 中使用std::atomic 代替bool flagmutex m 锁定机制吗?谢谢。
    • @MOJNICK 1. 是的。 2. 不,您可以使用atomicmutex,但不能同时使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    • 2016-12-23
    相关资源
    最近更新 更多