【问题标题】:Is using shared_ptr and weak_ptr to manage lifetime of std::function safe?使用 shared_ptr 和 weak_ptr 来管理 std::function 的生命周期是否安全?
【发布时间】:2011-12-23 20:45:25
【问题描述】:

我创建了一个围绕 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;
};

我在我的主应用程序类中保留了一个 UiTaskQueueRef 实例,并从我的应用程序的动画循环中调用 mUiTaskQueue-&gt;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 typedef:

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&lt;void(void)&gt;( std::bind( ... ) ) 是一件可以做的事情,并且使用 shared_ptr 管理是一件安全的事情吗?

【问题讨论】:

    标签: c++ boost-asio shared-ptr boost-thread std-function


    【解决方案1】:

    是的,这是安全的。

    代码:

    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。

    【讨论】:

    • 非常感谢!我认为我可以通过仅从将处理任务的 UI 线程中取消来避免竞争条件,但我会记住这段代码的线程版本。
    • 也感谢您的初始化提示!
    猜你喜欢
    • 2023-03-17
    • 2017-06-18
    • 1970-01-01
    • 2018-05-23
    • 2014-06-06
    • 2016-07-31
    • 2014-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多