Linux 定时器挂起信号

inc*_*ito 5 c c++ linux timer

我正在使用带有 SIGEV_THREAD 参数的 timer_create 创建 linux 计时器。

有时在我解除定时器并删除它后会调用回调。这会导致段错误,因为它尝试访问已删除的资源。

http://man7.org/linux/man-pages/man2/timer_delete.2.html

Linux手册说

timer_delete() 删除在timerid 中给出ID 的定时器。如果在此呼叫时定时器已布防,则在删除之前将其撤防。被删除的定时器产生的任何未决信号的处理是未指定的。

这基本上意味着我真的不知道回调是否会被调用,我没有办法取消它,也没有办法在清理资源之前强制传递挂起的信号。

class timer_wrapper
{
private:
    std::function<void()> callback_;
    timer_t timer_;

    static void timer_callback(sigval_t val)
    {
        static_cast<timer_wrapper*>(val.sival_ptr)->callback_();
    }
public:
    timer_wrapper(std::function<void()> callback, uint32_t interval_sec)
        : callback_(std::move(callback))
    {
        struct sigevent ev;
        ev.sigev_notify = SIGEV_THREAD;
        ev.sigev_signo = 0;
        ev.sigev_value.sival_ptr = this;
        ev.sigev_notify_function = &timer_wrapper::timer_callback;
        ev.sigev_notify_attributes = 0;
        timer_create(CLOCK_REALTIME, &ev, &timer_);

        struct itimerspec spec = {{0, 0}, {interval_sec, 0}};
        timer_settime(timer_, 0, &spec, nullptr);
    }

    ~timer_wrapper()
    {
        timer_delete(timer_);
    }
};
Run Code Online (Sandbox Code Playgroud)

如果 timer_wrapper 超出范围,我希望回调不会再被调用,但是有时会调用它,根据 man 的说法,这是预期的行为。

解决此问题的建议方法是什么?

Nom*_*mal 2

删除使用 创建的计时器时避免计时器生成的事件的最简单方法timer_create()是根本不避免它。相反,请使用volatile sig_atomic_t disarmed = 0;标志,并让事件函数在执行其他操作之前测试该标志,如果disarmed非零则立即返回。

这样,您首先设置计时器disarmed,然后删除计时器。

(最好使用原子内置函数,无论是旧式的__sync_fetch_and_add(&disarmed, 0)and __sync_fetch_and_and(&disarmed, 0),还是__atomic_load_n(&disarmed, __ATOMIC_SEQ_CST)and __atomic_exchange_n(&disarmed, 0, __ATOMIC_SEQ_CST),来访问标志,以确保正确的排序。)

对于SIGEV_SIGNAL,您可以首先阻止信号(使用pthread_sigmask()),删除计时器,使用sigtimedwait()零超时检查计时器删除期间是否引发了信号,最后恢复旧的信号掩码。

(我个人使用单个 POSIX 实时信号(SIGRTMIN+0to SIGRTMAX-0,在编译时定义)和以事件时间为键的最小堆(每个堆槽包含时间和对自定义超时/事件结构的引用)来处理大量事件,有专用线程。)