代码之家  ›  专栏  ›  技术社区  ›  asimes

从信号处理程序设置变量时,std::atomic或volatile是必需的吗

  •  0
  • asimes  · 技术社区  · 1 年前
    #include <unistd.h>
    
    #include <csignal>
    #include <exception>
    #include <functional>
    #include <iostream>
    
    std::function<void(int)> g_signalHandler;
    
    void signalWrapper(const int a) { g_signalHandler(a); }
    
    int main() {
        bool abort = false;
        g_signalHandler = [&abort](const int) {
            std::cout << "Abort" << std::endl;
            abort = true;
        };
    
        struct ::sigaction signalAction = {};
        signalAction.sa_handler = signalWrapper;
    
        if (
            ::sigaction(SIGHUP,  &signalAction, nullptr) != 0 ||
            ::sigaction(SIGINT,  &signalAction, nullptr) != 0 ||
            ::sigaction(SIGTERM, &signalAction, nullptr) != 0
        )
            throw std::exception();
    
        // Is it guaranteed that the new value of abort will be seen here without
        // the need of std::atomic<bool> or volatile bool?
        while (!abort)
            ::sleep(1);
    
        return 0;
    }
    

    假设一个单线程程序,C++17和Linux

    据我所知,使用正常 bool 是足够的 while (!abort) std::atomic<bool> / volatile bool 没有必要。我想确认这始终是正确的,我不必担心编译器会优化读取值

    1 回复  |  直到 1 年前
        1
  •  4
  •   user17732522    1 年前

    据我所知,对于while(!abort),使用普通bool就足够了,而std::atomic/volatile bool则不是必需的。我想确认这始终是正确的,我不必担心编译器会优化读取值

    不,这是错误的,会导致数据竞争,这总是未定义的行为。

    为了证明这种未定义的行为是如何合理地表现出来的:编译器可能会看到 abort 是非原子的 ::sleep 并不意味着任何同步。因此,可以得出结论 中止 如果最初是这样,它永远不会改变 false ,因为这会导致具有未定义行为的数据竞争,要么完全摆脱循环检查,要么保留 中止 在寄存器中,无需从内存中重新加载。在任何一种情况下,循环都不会终止。

    volatile bool 这也是错误的。 volatile 根据信号处理器标准的要求,这是不够的。

    不稳定的 但是,确实保证了代码中的每次读写都会转化为加载和存储 不稳定的 不保证这些加载/存储的原子性。(但是,特定的编译器/平台组合可能会做出这样的保证,因此 不稳定的 可用于实现原子操作。)

    在信号处理程序中,您保证能够使用定义的行为访问的唯一非局部变量是(非线程局部)

    • 类型对象 volatile std::atomic_sig_t (并通过以下方式访问 不稳定的 glvalue);只有在信号处理程序和信号处理程序运行的线程之间独占共享(没有其他同步)时,其他线程才能共享
    • 无锁原子对象; std::atomic_flag 保证无锁;哪一个 std::atomic 专业化是无锁的,依赖于平台,可以用 is_lock_free is_always_lock_free 成员 std::atomic

    此外,默认情况下,库函数在信号处理程序中使用也不安全。C/C++/POSIX标准中只有特定的例外。明确地 std::function 并按如下方式流式传输IO std::cout << 在信号处理程序中不安全,也会导致未定义的行为。

    请参阅 https://en.cppreference.com/w/cpp/utility/program/signal 因为C++标准对信号处理程序施加了要求,以便它们根据标准具有定义良好的行为。

    看看 https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03 对于列表 异步信号安全 POSIX环境中的函数。