You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS守护进程创建多个CFRunLoopTimer时callback2不触发问题

macOS守护进程多定时器失效问题排查

问题描述

我正尝试为macOS守护进程创建多个周期性定时器,编写了MyTimer类:

struct MyTimer
{

MyTimer()
{
}

~MyTimer()
{
    stopTimer();
}


bool setTimer(size_t nmsPeriod, //Period in milliseconds
              void (*pfn)(void) //Function to call when timer fires
             )
{
    stopTimer();

    CFTimeInterval fInterval = ((CFTimeInterval)nmsPeriod) / 1000.0;

    CFRunLoopTimerContext ctx = {};
    ctx.info = this;

    _pfnCallback = pfn;

    _refTmr = CFRunLoopTimerCreate(kCFAllocatorDefault,
                                   CFAbsoluteTimeGetCurrent() + fInterval,
                                   fInterval,
                                   0, 0,
                                   _callbackTmr,
                                   &ctx);

    if(!_refTmr)
        return false;

    CFRunLoopAddTimer(CFRunLoopGetCurrent(), _refTmr, kCFRunLoopCommonModes);

    return true;
}

void stopTimer()
{
    if(_refTmr)
    {
        CFRunLoopTimerInvalidate(_refTmr);
            
        CFRelease(_refTmr);
            
        _refTmr = NULL;
    }
}


protected:
    static void _callbackTmr(CFRunLoopTimerRef timer, void *info)
    {
        MyTimer* pThis = (MyTimer*)info;
        pThis->_pfnCallback();
    }


private:

    CFRunLoopTimerRef _refTmr = NULL;
    void (*_pfnCallback)(void) = NULL;
};

在守护进程中定义两个全局实例:

MyTimer gTmr1;
MyTimer gTmr2;

启动时初始化定时器:

//Set 1st timer 2 minutes from now
gTmr1.setTimer(2 * 60 * 1000, callback1);

//Set 2nd timer 300 seconds from now
gTmr2.setTimer(300 * 1000, callback2);

//And then reset the 2nd timer 2 seconds from now
gTmr2.setTimer(2 * 1000, callback2);

// ...

//Enter the run-loop...
CFRunLoopRun();

回调函数:

static void callback1(void)
{
   log("Callback 1 fired");
}

static void callback2(void)
{
   log("Callback 2 fired");
}

实际运行时,只有callback1触发,callback2始终不执行。


问题分析与修复

核心问题:栈上CFRunLoopTimerContext的生命周期失效

在setTimer方法中,你创建了栈局部变量CFRunLoopTimerContext ctx,当setTimer执行完毕返回后,这个栈对象会被系统销毁并释放内存。但CFRunLoopTimer会持有该context的指针,后续定时器触发时访问已失效的内存,引发未定义行为——具体表现为callback2无法正常执行(第一次设置gTmr2的timer时context已失效,重置时又创建了新的栈context,同样会在方法结束后失效)。

修复方案

将CFRunLoopTimerContext改为MyTimer的成员变量,确保其生命周期与定时器实例一致:

  1. 在MyTimer的private成员中添加context变量:
private:
    CFRunLoopTimerRef _refTmr = NULL;
    void (*_pfnCallback)(void) = NULL;
    CFRunLoopTimerContext _ctx = {}; // 添加这一行
  1. 修改setTimer方法中的context初始化逻辑:
bool setTimer(size_t nmsPeriod, void (*pfn)(void))
{
    stopTimer();

    CFTimeInterval fInterval = ((CFTimeInterval)nmsPeriod) / 1000.0;

    // 重置context,避免旧数据干扰
    memset(&_ctx, 0, sizeof(_ctx));
    _ctx.info = this;

    _pfnCallback = pfn;

    _refTmr = CFRunLoopTimerCreate(kCFAllocatorDefault,
                                  CFAbsoluteTimeGetCurrent() + fInterval,
                                  fInterval,
                                  0, 0,
                                  _callbackTmr,
                                  &_ctx);

    if(!_refTmr)
        return false;

    CFRunLoopAddTimer(CFRunLoopGetCurrent(), _refTmr, kCFRunLoopCommonModes);

    return true;
}

额外说明

  • 全局MyTimer实例的生命周期覆盖整个守护进程运行期,因此成员变量_ctx不会提前失效,定时器触发时能正确访问到this指针和回调函数。
  • 当前的stopTimer方法逻辑正确,能在重置定时器时销毁旧定时器并释放资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者c00000fd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:45:31