Безопасно ли использовать shared_ptr и weak_ptr для управления временем жизни std :: function? - PullRequest
4 голосов
/ 07 ноября 2011

Я создал оболочку над boost :: asio :: io_service для обработки асинхронных задач в потоке GUI приложения OpenGL.

Задачи могут создаваться из других потоков, поэтому 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->update() из цикла анимации моего приложения.

Я бы хотел расширить функциональность этого класса, чтобы разрешить отмену задачи. Моя предыдущая реализация (с использованием почти того же интерфейса) возвращала числовой идентификатор для каждой задачи и позволяла отменять задачи с использованием этого идентификатора. Но теперь управление очередью и связанной блокировкой обрабатывается 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_ptr до mTask, тогда io_service будет выполнять задачу. Если я позвоню reset на mTask, weak_ptr не сможет заблокироваться, и задание будет пропущено по желанию.

Мой вопрос - действительно вопрос доверия со всеми этими новыми инструментами: new std::function<void(void)>( std::bind( ... ) ) нормально ли это делать и безопасно ли управлять с помощью shared_ptr?

1 Ответ

2 голосов
/ 08 ноября 2011

Да, это безопасно.

Для кода:

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, поддерживая обратный вызов живым, и в результате вы иногда будете видеть обратные вызовы, даже если вы пошли вниз по пути кода, сбрасывающего обратный вызов shared_ptr.

...