7

我在一段实际代码中遇到问题,其中属于已删除类的函数由 a 调用boost::asio::deadline_timer,偶尔会导致分段错误。

我遇到的问题是,deadline_timer 的删除是从同一个 io_service 上的另一个计时器运行的。删除第一个deadline_timer将触发对要运行的函数的最终调用,但会出现boost::asio::error::operation_aborted错误。但是,这只能io_service在删除完成后(相同)安排,但到那时对象已经被删除,因此不再有效。

所以我的问题是:我怎样才能防止这种情况发生?

以下是具有相同故障的简化示例:

//============================================================================
// Name        : aTimeToKill.cpp
// Author      : Pelle
// Description : Delete an object using a timer, from a timer
//============================================================================

#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace boost;

struct TimeBomb
{
    bool m_active;
    asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << (int)this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << (int)this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
        }
    }
};

struct BomberMan
{
    asio::deadline_timer m_selfDestructTimer;
    TimeBomb* myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb = new TimeBomb(ioService);
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        delete myBomb;
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}


./aTimeToKill
BomberMan ready 
Bomb placed @9c27198
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
Defusing TimeBomb
Bomb defused @9c27198
Timer aborted: Operation canceled @9c27198

删除后打印最后一行,说明我的问题。

4

4 回答 4

6

解决这个问题的典型方法是使用shared_ptr

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>

using namespace std;

struct TimeBomb : public boost::enable_shared_from_this<TimeBomb>
{
    bool m_active;
    boost::asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
    }

    void start()
    {
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
    }

    void stop()
    {
        m_runTimer.cancel();
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
        }
    }
};

struct BomberMan
{
    boost::asio::deadline_timer m_selfDestructTimer;
    boost::shared_ptr<TimeBomb> myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb.reset( new TimeBomb(ioService) );
        myBomb->start();
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        myBomb->stop();
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}
于 2012-10-25T14:44:18.920 回答
2

这就是为什么你有boost::shared_ptrboost::enable_shared_from_this。像这样继承TimeBombboost::enable_shared_from_this

struct TimeBomb : public boost::enable_shared_from_this< TimeBomb >
{
...
}

实例化一个共享 ptr 而不是裸 ptr:

boost::shared_ptr< TimeBomb > myBomb;
...
myBomb.reset( new TimeBomb(ioService) );

最后在TimeBomb使用shared_from_this()而不是this构造处理程序。

m_runTimer.async_wait( boost::bind( &TimeBomb::executeStepFunction, shared_from_this(), _1));

当然,TimeBomb类应该公开一个cancel方法,通过它您可以取消异步操作,而不是通过删除,或者在这种情况下,重置 shared_ptr。

于 2012-10-25T14:38:55.333 回答
0

来自 Sam Miller 的 shared_ptr 答案有效,因为 shared_ptr 的使用使 TimeBomb 在 BomberMan 的整个生命周期中一直存在。这对你来说可能没问题,也可能不是。

一个更完整的解决方案的建议是从工厂获取 TimeBomb 实例,然后在完成后将它们释放回,而不是显式地更新和删除它们(将它们作为标准指针,而不是 shared_ptrs 因为你不拥有它们即使您正在控制生命周期)。工厂可以保留它们,直到它们被取消,然后为您删除它们。保持 Sam Miller 的 stop() 函数不变。

为了实现这一点,从接口派生工厂

class ITimeBombObserver 
{
public:
    virtual void AllOperationsComplete(TimeBomb& TmBmb)=0;
};

将您的工厂作为 ITimeBombObserver 传递给每个 TimeBomb 构造,并取消 TimeBomb 调用此函数。工厂可以在每次创建或释放 TimeBomb 时清理“使用过的”TimeBomb,或者使用计划清理或其他方法,无论哪种方法最适合您的应用程序。

使用此方法,您的 BomberMan 甚至不需要在 defuseBomb() 中显式释放 TimeBomb,如果不需要,对 stop() 的调用可以自动释放(尽管在这种情况下,您仍然应该将指针设为空,因为它变得有效此时无法使用)。这是否是一个好主意取决于您的实际问题,因此我将由您决定。

于 2012-10-25T15:18:09.823 回答
0

对于一个非常简单的修复,这个怎么样?(我只包含了您需要更改的部分)

它之所以有效,是因为您只能在计时器取消时访问堆栈变量。当然,您根本不需要在析构函数中回调处理程序,但我假设您的真实代码出于任何原因都需要这样做。

~TimeBomb()
{
    m_active = false;
    executeStepFunction(boost::asio::error::interrupted);
    m_runTimer.cancel();
    cout << "Bomb defused @"<< hex << (int)this << endl;
}

void executeStepFunction(const boost::system::error_code& error)
{
    // Canceled timer
    if (error ==  boost::asio::error::operation_aborted)
    {
        return;
    }
    if (error ==  boost::asio::error::interrupted)
    {
        std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
        return;
    }
...
于 2012-10-26T11:43:55.750 回答