使用悬浮的协程进行适当的清理

时间:2014-11-04 16:04:24

标签: c++ boost asynchronous boost-asio

我想知道在这种情况下最好的(最干净,最难搞乱的)清理方法是什么。

void MyClass::do_stuff(boost::asio::yield_context context) {
  while (running_) {
    uint32_t data = async_buffer->Read(context);
    // do other stuff
  }
}

读取是一个异步调用,直到有数据要读取,然后返回该数据。如果我想删除MyClass的这个实例,我怎样才能确保我这样做呢?假设这里的异步等待是通过deadline_timer的async_wait执行的。如果我取消该事件,我仍然需要等待线程完成执行“其他东西”才知道事情处于良好状态(我无法加入线程,因为它是属于io服务的线程也可能正在处理其他工作)。我可以这样做:

MyClass::~MyClass() {
  running_ = false;
  read_event->CancelEvent(); // some way to cancel the deadline_timer the Read is waiting on
  boost::mutex::scoped_lock lock(finished_mutex_);
  if (!finished_) {
    cond_.wait(lock);
  }
  // any other cleanup
}

void MyClass::do_stuff(boost::asio::yield_context context) {
  while (running_) {
    uint32_t data = async_buffer->Read(context);
    // do other stuff
  }
  boost::mutex::scoped_lock lock(finished_mutex_);
  finished_ = true;
  cond.notify();
}

但是我希望这些堆栈协程尽可能易于使用,并且人们不会直截了当地认识到这种情况存在以及需要做些什么才能确保正确清理事情。有没有更好的办法?在更基础的层面上,我试图在这里做错了吗?

另外,对于这个事件(我所拥有的与Tanner的答案基本相同here)我需要以一种我必须保持一些额外状态的方式取消它(一个真正的取消与正常取消用于触发事件) - 如果有多个逻辑等待同一事件,这将是不合适的。很想知道是否有更好的方法来模拟与协程挂起/恢复一起使用的异步事件。

感谢。

编辑:谢谢@Sehe,拍了一个工作实例,我想这说明了我的目标:

class AsyncBuffer {
public:
  AsyncBuffer(boost::asio::io_service& io_service) :
  write_event_(io_service) {
    write_event_.expires_at(boost::posix_time::pos_infin);
  }

  void Write(uint32_t data) {
    buffer_.push_back(data);
    write_event_.cancel();
  }

  uint32_t Read(boost::asio::yield_context context) {
    if (buffer_.empty()) {
      write_event_.async_wait(context);
    }
    uint32_t data = buffer_.front();
    buffer_.pop_front();
    return data;
  }

protected:
  boost::asio::deadline_timer write_event_;
  std::list<uint32_t> buffer_;
};

class MyClass {
public:
  MyClass(boost::asio::io_service& io_service) :
      running_(false), io_service_(io_service), buffer_(io_service) {
  }

  void Run(boost::asio::yield_context context) {
    while (running_) {
      boost::system::error_code ec;
      uint32_t data = buffer_.Read(context[ec]);
      // do something with data
    }
  }

  void Write(uint32_t data) {
    buffer_.Write(data);
  }

  void Start() {
    running_ = true;
    boost::asio::spawn(io_service_, boost::bind(&MyClass::Run, this, _1));
  }

protected:
  boost::atomic_bool running_;
  boost::asio::io_service& io_service_;
  AsyncBuffer buffer_;
};

所以在这里,假设缓冲区为空,MyClass :: Run当前在调用Read时暂停,因此有一个deadline_timer.async_wait正在等待事件触发以恢复该上下文。是时候销毁这个MyClass实例了,所以我们如何确保它干净利落。

1 个答案:

答案 0 :(得分:1)

更典型的方法是将boost::enable_shared_from_thisMyClass一起使用,并运行绑定到共享指针的方法。

Boost Bind支持透明地绑定到boost::shared_ptr<MyClass>

这样,只有当最后一个用户消失时,才能自动运行析构函数。


如果您创建了一个SSCCE,我很乐意改变它,以显示我的意思。


<强>更新

致SSCCEE:一些评论:

  • 我想象了一个运行IO服务的线程池
  • MyClass直接调用AsyncBuffer成员函数的方式不是线程安全的。实际上没有线程安全的方法来取消生产者线程 [1] 之外的事件,因为生产者已经访问了Write的缓冲区。这可以使用一个链来缓解(在当前的设置中我不知道MyClass如何可能是线程安全的)。或者,查看活动对象模式(T​​anner在SO上有一个很好的答案 [2]

    为了简单起见,我在这里选择了strand方法,所以我们这样做:

    void MyClass::Write(uint32_t data) {
        strand_.post(boost::bind(&AsyncBuffer::Write, &buffer_, data));
    }
    
  • 你问

      

    另外,对于这个事件(我所拥有的与Tanner的答案基本相同)我需要以一种我必须保持一些额外状态的方式取消它(a true取消与用于触发事件的正常取消相比)

    这种状态最自然的地方是deadline_timer常用的地方:截止日期。通过重置计时器来停止缓冲区:

    void AsyncBuffer::Stop() { // not threadsafe!
        write_event_.expires_from_now(boost::posix_time::seconds(-1));
    }
    

    这会立即取消计时器,但可以检测到,因为截止日期已过去。

这是一个简单的演示,其中包含一组IO服务线程,一个&#34;生产者协程&#34;产生随机数和&#34;狙击线程&#34;在2秒后狙击MyClass::Run协程。主线是狙击线程。

查看 Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/atomic.hpp>
#include <list>
#include <iostream>

// for refcounting:
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>

namespace asio = boost::asio;

class AsyncBuffer {
    friend class MyClass;
protected:
    AsyncBuffer(boost::asio::io_service &io_service) : write_event_(io_service) {
        write_event_.expires_at(boost::posix_time::pos_infin);
    }

    void Write(uint32_t data) {
        buffer_.push_back(data);
        write_event_.cancel();
    }

    uint32_t Read(boost::asio::yield_context context) {
        if (buffer_.empty()) {
            boost::system::error_code ec;
            write_event_.async_wait(context[ec]);

            if (ec != boost::asio::error::operation_aborted || write_event_.expires_from_now().is_negative())
            {
                if (context.ec_)
                    *context.ec_ = boost::asio::error::operation_aborted;
                return 0;
            }
        }

        uint32_t data = buffer_.front();
        buffer_.pop_front();
        return data;
    }

    void Stop() {
        write_event_.expires_from_now(boost::posix_time::seconds(-1));
    }

private:
    boost::asio::deadline_timer write_event_;
    std::list<uint32_t> buffer_;
};

class MyClass : public boost::enable_shared_from_this<MyClass> {
    boost::atomic_bool stopped_;
public:
    MyClass(boost::asio::io_service &io_service) : stopped_(false), buffer_(io_service), strand_(io_service) {}

    void Run(boost::asio::yield_context context) {
        while (!stopped_) {
            boost::system::error_code ec;

            uint32_t data = buffer_.Read(context[ec]);

            if (ec == boost::asio::error::operation_aborted)
                break;

            // do something with data
            std::cout << data << " " << std::flush;
        }
        std::cout << "EOF\n";
    }

    bool Write(uint32_t data) { 
        if (!stopped_) {
            strand_.post(boost::bind(&AsyncBuffer::Write, &buffer_, data));
        }
        return !stopped_;
    }

    void Start() {
        if (!stopped_) {
            stopped_ = false;
            boost::asio::spawn(strand_, boost::bind(&MyClass::Run, shared_from_this(), _1));
        }
    }

    void Stop() {
        stopped_ = true;
        strand_.post(boost::bind(&AsyncBuffer::Stop, &buffer_));
    }

    ~MyClass() { 
        std::cout << "MyClass destructed because no coroutines hold a reference to it anymore\n";
    }

protected:
    AsyncBuffer buffer_;
    boost::asio::strand strand_;
};

int main()
{
    boost::thread_group tg;
    asio::io_service svc;

    {
        // Start the consumer:
        auto instance = boost::make_shared<MyClass>(svc); 
        instance->Start();

        // Sniper in 2 seconds :)
        boost::thread([instance]{ 
                boost::this_thread::sleep_for(boost::chrono::seconds(2));
                instance->Stop();
                }).detach();

        // Start the producer:
        auto producer_coro = [instance, &svc](asio::yield_context c) { // a bound function/function object in C++03
            asio::deadline_timer tim(svc);

            while (instance->Write(rand())) {
                tim.expires_from_now(boost::posix_time::milliseconds(200));
                tim.async_wait(c);
            }
        };

        asio::spawn(svc, producer_coro);

        // Start the service threads:
        for(size_t i=0; i < boost::thread::hardware_concurrency(); ++i)
            tg.create_thread(boost::bind(&asio::io_service::run, &svc));
    }

    // now `instance` is out of scope, it will selfdestruct after the snipe
    // completed
    boost::this_thread::sleep_for(boost::chrono::seconds(3)); // wait longer than the snipe
    std::cout << "This is the main thread _after_ MyClass self-destructed correctly\n";

    // cleanup service threads
    tg.join_all();
}

[1] 逻辑线程,这可能是在不同线程上恢复的协程

[2] boost::asio and Active Object