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

Lambda中指针引用问题:定时器任务无法执行的解决方法

问题分析与解决方案

首先咱们来拆解下你遇到的两个核心问题:为什么直接用指针捕获会导致func执行时无输出,以及如何既保留修改原对象的能力(比如取消任务)又能让定时器正常工作。

一、原代码的问题根源

  1. 无限递归陷阱
    你在at方法里把原taskinfo的func替换成了捕获taskinfo**的Lambda,然后把原对象的拷贝推入队列。当队列里的任务执行时,这个Lambda会调用(*task)->func——但此时原对象的func已经是这个Lambda本身了!这就会触发无限递归创建线程,最终导致程序无响应或者栈溢出,看起来像是func为空没有输出。

  2. 任务队列与原对象脱节
    你的priority_queue存储的是taskinfo的拷贝对象,而非指针。这意味着你调用cancel方法修改原对象的cancel标志时,队列里的拷贝对象的cancel不会同步变化,根本达不到取消任务的目的——这也是你需要修改原引用对象的核心诉求无法实现的原因。

另外你提到“拷贝本地对象后功能正常”,其实这是侥幸的未定义行为:Lambda捕获了本地taskinfo的引用,但本地对象在at方法返回后就销毁了,Lambda里的引用变成了悬空引用,只是测试环境下内存没被覆盖才暂时正常,实际运行中随时会崩溃。

二、正确的解决方案:用智能指针共享对象实例

要解决这两个问题,核心是让任务队列里的任务和原对象是同一个实例,同时避免递归调用。我们可以用std::shared_ptr来存储taskinfo,确保队列和外部共享同一个对象。

修改后的完整代码:

#include <functional>
#include <chrono>
#include <future>
#include <queue>
#include <thread>
#include <memory>
#include <sstream>
#include <assert.h>
#include <iostream>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>
#include <limits.h>

#define TIMER_NO_TASK_SLEEP_TIME 100

struct taskinfo {
    long int id;
    bool cancel;
    std::function<void()> func;
    std::chrono::system_clock::time_point time;
    std::chrono::system_clock::duration interval;

    taskinfo() {}

    // 为priority_queue定制的比较器(实现小顶堆,最早的任务优先执行)
    struct Compare {
        bool operator()(const std::shared_ptr<taskinfo>& a, const std::shared_ptr<taskinfo>& b) const {
            return a->time > b->time;
        }
    };

    taskinfo(long int id, std::function<void()>&& f, const std::chrono::system_clock::time_point& t) 
        : id(id), func(std::move(f)), time(t) { 
        cancel = false; 
    }
};

class TimerTask {
private:
    // 存储shared_ptr,确保队列与外部共享同一个taskinfo实例
    std::priority_queue<std::shared_ptr<taskinfo>, std::vector<std::shared_ptr<taskinfo>>, taskinfo::Compare> tasks;
    std::unique_ptr<std::thread> thread;
    bool keepRunning;

public:
    TimerTask() : keepRunning(true), thread(new std::thread([this]() {
        while(keepRunning) {
            auto now = std::chrono::system_clock::now();
            while(!tasks.empty() && tasks.top()->time <= now) {
                auto task = tasks.top();
                tasks.pop();
                if(!task->cancel) {
                    // 启动线程执行原func,避免阻塞定时器主线程
                    std::thread([func = std::move(task->func)]() {
                        func();
                    }).detach();
                }
            }
            if(tasks.empty()) {
                std::this_thread::sleep_for(std::chrono::milliseconds(TIMER_NO_TASK_SLEEP_TIME));
            } else {
                std::this_thread::sleep_for(tasks.top()->time - std::chrono::system_clock::now());
            }
        }
    })) {}

    ~TimerTask() {
        keepRunning = false;
        thread->join();
    }

    // 直接传入shared_ptr,推入队列
    void at(std::shared_ptr<taskinfo> task){
        tasks.push(task);
    }

    // 修改原对象的cancel标志,队列里的实例会同步生效
    void cancel(std::shared_ptr<taskinfo> task){
        task->cancel = true;
    }
};

void timeoutFunc(int id) {
    std::cout << "Request " << id << " Timeout.... Executed Timeout Function Time:" 
              << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
}

int main(int argc, char* argv[]) {
    if(argc != 2) {
        std::cout << "\n Usage <Process> <Timeout>" << std::endl;
        return 0;
    }
    int timeout = atoi(argv[1]);
    TimerTask tT;
    std::vector<std::shared_ptr<taskinfo>> requests;
    requests.reserve(1000);

    for(int i=1; i <=5; i++ ) {
        auto t = std::make_shared<taskinfo>(i, [i]{ timeoutFunc(i); }, 
                                           std::chrono::system_clock::now() + std::chrono::milliseconds(timeout));
        tT.at(t);
        requests.push_back(t);
        std::cout << "Request " << i << " Registered.... Time:" 
                  << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
    }

    while(1) sleep(60);
    return 0;
}

三、关键修改点说明

  1. 用std::shared_ptr<taskinfo>存储任务
    队列里不再存储对象拷贝,而是存储智能指针,确保外部和队列共享同一个taskinfo实例。这样调用cancel方法修改原对象的cancel标志时,队列里的任务也能看到这个变化,真正实现取消功能。

  2. 移除原Lambda递归逻辑
    定时器线程在执行任务时,直接提取原func并启动线程执行,避免了原代码中修改func导致的递归问题。同时用std::move转移func的所有权,避免不必要的拷贝。

  3. 定制priority_queue的比较器
    因为现在存储的是shared_ptr,需要重新定义比较逻辑,确保priority_queue按照任务的执行时间从小到大排序(小顶堆)。

这样修改后,你既可以通过cancel方法修改原对象的标志来取消任务,定时器也能正常执行任务输出结果了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:43:08