正在使用shared_ptr和weak_ptr来管理std::函数安全的生命周期

Is using shared_ptr and weak_ptr to manage lifetime of std::function safe?

本文关键字:ptr std 函数 安全 周期 生命 管理 weak shared      更新时间:2023-10-16

我在boost::asio::io_service周围创建了一个包装器来处理OpenGL应用程序的GUI线程上的异步任务。

任务可以从其他线程创建,因此boost::asio似乎非常适合此目的,并且意味着我不需要编写带有相关互斥锁和锁定的自己的任务队列。我想保持每帧的工作低于可接受的阈值(例如5毫秒),所以我调用poll_one,直到超出预期的预算,而不是调用run。据我所知,这要求我在发布新任务时调用reset,这似乎工作得很好。

由于它很短,下面是全部内容,sans #include:

typedef std::function<void(void)> VoidFunc;
typedef std::shared_ptr<class UiTaskQueue> UiTaskQueueRef;
class UiTaskQueue {
public:
    static UiTaskQueueRef create()
    {
        return UiTaskQueueRef( new UiTaskQueue() );
    }
    ~UiTaskQueue() {} 
    // normally just hand off the results of std/boost::bind to this function:
    void pushTask( VoidFunc f )
    {
        mService.post( f );
        mService.reset();
    }
    // called from UI thread; defaults to ~5ms budget (but always does one call)        
    void update( const float &budgetSeconds = 0.005f )
    {
        // getElapsedSeconds is a utility function from the GUI lib I'm using
        const float t = getElapsedSeconds();
        while ( mService.poll_one() && getElapsedSeconds() - t < budgetSeconds );
    }
private:
    UiTaskQueue() {}
    boost::asio::io_service mService;
};

我在我的主应用程序类中保留了一个UiTaskQueueRef的实例,并在我的应用程序的动画循环中调用mUiTaskQueue->update()

我想扩展这个类的功能,以允许取消任务。我之前的实现(使用几乎相同的接口)为每个任务返回一个数字ID,并允许使用这个ID取消任务。但是现在队列和相关锁定的管理是由boost::asio处理的,我不确定如何最好地做到这一点。

我已经尝试了包装任何任务,我可能想要在shared_ptr中取消,并制作一个包装器对象,该对象将weak_ptr存储到任务并实现()操作符,以便将其传递给io_service。它看起来像这样:

struct CancelableTask {
    CancelableTask( std::weak_ptr<VoidFunc> f ): mFunc(f) {}
    void operator()(void) const {
        std::shared_ptr<VoidFunc> f = mFunc.lock();
        if (f) {
            (*f)();
        }
    }
    std::weak_ptr<VoidFunc> mFunc;
};

我有一个重载的pushTask方法,看起来像这样:

void pushTask( std::weak_ptr<VoidFunc> f )
{
    mService.post( CancelableTask(f) );
    mService.reset();
}

然后将可取消的任务发送到队列,使用:

std::function<void(void)> *task = new std::function<void(void)>( boost::bind(&MyApp::doUiTask, this) );
mTask = std::shared_ptr< std::function<void(void)> >( task );
mUiTaskQueue->pushTask( std::weak_ptr< std::function<void(void)> >( mTask ) );

如果您喜欢,也可以使用VoidFunc类型定义:

VoidFunc *task = new VoidFunc( std::bind(&MyApp::doUiTask, this) );
mTask = std::shared_ptr<VoidFunc>( task );
mUiTaskQueue->pushTask( std::weak_ptr<VoidFunc>( mTask ) );

只要我保持shared_ptrmTask左右,那么io_service将执行任务。如果我在mTask上调用reset,那么weak_ptr无法锁定,任务将按预期跳过。

我的问题是对所有这些新工具的信心:new std::function<void(void)>( std::bind( ... ) )是一件可以做的事情吗?用shared_ptr管理是一件安全的事情吗?

是的,这是安全的。

对于代码:

VoidFunc *task = new VoidFunc( std::bind(&MyApp::doUiTask, this) );
mTask = std::shared_ptr<VoidFunc>( task );

只做:

mTask.reset(new VoidFunc( std::bind(&MyApp::doUiTask, this) ) );

(及其他地方)。

请记住,您需要处理竞争条件,在您重置shared_ptr之前,一个线程可能会在weak_ptr上获得锁,从而使回调保持活动状态,因此您偶尔会看到回调,即使您沿着代码路径重置了shared_ptr回调。