使用shared_ptr和weak_ptr管理std :: function的生存期是否安全?

Ran*_*Etc 5 c++ boost-thread shared-ptr boost-asio std-function

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

任务可能是从其他线程创建的,因此boost::asio看来是理想的选择,这意味着我不需要编写带有关联互斥锁和锁定的任务队列。我想将每个帧上完成的工作保持在可接受的阈值(例如5毫秒)以下,因此我要打电话poll_one直到超出所需的预算为止,而不是打电话run。据我所知,这要求我reset在发布新任务时都打电话给我,这似乎效果很好。

由于很短,所以这里是整件事,无#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;
};
Run Code Online (Sandbox Code Playgroud)

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

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

我通过将可能要取消的所有任务包装在a中,shared_ptr并制作了一个包装对象来进行尝试,该包装对象将a存储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;
};
Run Code Online (Sandbox Code Playgroud)

然后,我的pushTask方法的重载如下所示:

void pushTask( std::weak_ptr<VoidFunc> f )
{
    mService.post( CancelableTask(f) );
    mService.reset();
}
Run Code Online (Sandbox Code Playgroud)

然后,我使用以下命令将可取消的任务发布到队列中:

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 ) );
Run Code Online (Sandbox Code Playgroud)

或使用VoidFunctypedef,如果您愿意:

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

只要我保持shared_ptrmTask周围那么io_service将执行的任务。如果我拨打电话resetmTaskweak_ptr无法锁定,并且可以根据需要跳过任务。

我的问题确实是对所有这些新工具的信心之一:new std::function<void(void)>( std::bind( ... ) )要做的事是一件好事,而使用a进行管理是一件安全的事shared_ptr吗?

jan*_*anm 2

是的,这很安全。

对于代码:

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

做就是了:

mTask.reset(new VoidFunc( std::bind(&MyApp::doUiTask, this) ) );
Run Code Online (Sandbox Code Playgroud)

(和其他地方)。

请记住,您需要处理竞争条件,即在您重置shared_ptr以保持回调活动之前,tread可能会锁定weak_ptr,因此,即使您执行了代码,您偶尔也会看到回调重置回调shared_ptr的路径。