简体   繁体   English

如何在 C++ 中处理线程安全的回调注册和执行?

[英]How to handle thread-safe callback registration and execution in C++?

For example I've an EventGenerator class that call IEventHandler::onEvent for all registered event handlers:例如,我有一个EventGenerator类,它为所有注册的事件处理程序调用IEventHandler::onEvent

class IEventHandler {
public: virtual void onEvent(...) = 0;
};


class EventGenerator {
private: 
   std::vector<IEventHandler*> _handlers;
   std::mutex _mutex; // [1]
public:
   void AddHandler(IEventHandler* handler) {
      std::lock_guard<std::mutex> lck(_mutex); // [2]
      _handlers.push_back(handler);
   }
   void RemoveHanler(IEventHandler* handler) {
      std::lock_guard<std::mutex> lck(_mutex); // [3]
      // remove from "_handlers"
   }
private:
   void threadMainTask() {

      while(true) {

         // Do some work ...

         // Post event to all registered handlers
         {
            std::lock_guard<std::mutex> lck(_mutex); // [4]
            for(auto& h : _handlers) { h->onEvent(...); )
         }

         // Do some work ...

      }
    }

The code should be thread safe in the following manner:代码应该以下列方式是线程安全的:

  • one thread is executing the EventGenerator::threadMainTask一个线程正在执行EventGenerator::threadMainTask
  • many threads might access EventGenerator::AddHandler and EventGenerator::RemoveHandler APIs.许多线程可能会访问EventGenerator::AddHandlerEventGenerator::RemoveHandler API。

To support this, I have the following synchonization (see comment in the code):为了支持这一点,我有以下同步(请参阅代码中的注释):

  • [1] is the mutex that protects the vector _handlers from multiple thread access. [1]是保护向量_handlers免受多线程访问的互斥锁。
  • [2] and [3] are protect adding or removing handlers simultaneously. [2][3]保护同时添加或删除处理程序。
  • [4] is preventing from changing the vector while the main thread is posting events. [4]阻止在主线程发布事件时更改向量。

This code works until... If for some reason, during the execution of IEventHandler::onEvent(...) the code is trying to call EventManager::RemoveHandler or EventManager::AddHandler .这段代码一直工作到...如果由于某种原因,在IEventHandler::onEvent(...)的执行过程中,代码试图调用EventManager::RemoveHandlerEventManager::AddHandler The result is runtime exception.结果是运行时异常。

What is the best approach to handle registration of the event handlers and executing the event handler callback in the thread safe manner?以线程安全的方式处理事件处理程序注册和执行事件处理程序回调的最佳方法是什么?




>> UPDATE << >> 更新 <<

So based on the inputs, I've updated to the following design:因此,根据输入,我已更新为以下设计:

class IEventHandler {
public: virtual void onEvent(...) = 0;
};

class EventDelegate {
private: 
   IEventHandler* _handler;
   std::atomic<bool> _cancelled;
public:
   EventDelegate(IEventHandler* h) : _handler(h), _cancelled(false) {};
   void Cancel() { _cancelled = true; }
   void Invoke(...) { if (!_cancelled) _handler->onEvent(...); }
}

class EventGenerator {
private: 
   std::vector<std::shared_ptr<EventDelegate>> _handlers;
   std::mutex _mutex;
public:
   void AddHandler(std::shared_ptr<EventDelegate> handler) {
      std::lock_guard<std::mutex> lck(_mutex);
      _handlers.push_back(handler);
   }
   void RemoveHanler(std::shared_ptr<EventDelegate> handler) {
      std::lock_guard<std::mutex> lck(_mutex);
      // remove from "_handlers"
   }
private:
   void threadMainTask() {

      while(true) {

         // Do some work ...

         std::vector<std::shared_ptr<EventDelegate>> handlers_copy;

         {
            std::lock_guard<std::mutex> lck(_mutex);
            handlers_copy = _handlers;
         }

         for(auto& h : handlers_copy) { h->Invoke(...); )

         // Do some work ...

      }
    }

As you can see, there is additional class EventDelegate that have two purposes:如您所见,还有一个额外的类EventDelegate有两个目的:

  1. hold the event callback保持事件回调
  2. enable to cancel the callback启用取消回调

In the threadMainTask , I'm using a local copy of the std::vector<std::shared_ptr<EventDelegate>> and I'm releasing the lock before invoking the callbacks.threadMainTask ,我使用std::vector<std::shared_ptr<EventDelegate>>的本地副本,并且在调用回调之前释放锁。 This approach solves an issue when during the IEventHandler::onEvent(...) the EventGenerator::{AddHandler,RemoveHanler} is called.这种方法解决了在IEventHandler::onEvent(...)期间IEventHandler::onEvent(...) EventGenerator::{AddHandler,RemoveHanler}的问题。

Any thoughts about the new design?对新设计有什么想法吗?

Copy-on-Write vector implemented on atomic swap of shared_ptr's (in assumptions callback registration is occurring far less frequently than events the callbacks are notified about):在 shared_ptr 的原子交换上实现的写时复制向量(假设回调注册发生的频率远低于回调通知的事件):

using callback_t = std::shared_ptr<std::function<void(event_t const&)> >;
using callbacks_t = std::shared_ptr<std::vector<callback_t> >;
callbacks_t callbacks_;
mutex_t mutex_; // a mutex of your choice

void register(callback_t cb)
{
    // the mutex is to serialize concurrent callbacks registrations
    // this is not always necessary, as depending on the application
    // architecture, single writer may be enforced by design
    scoped_lock lock(mutex_);

    auto callbacks = atomic_load(&callbacks_);

    auto new_callbacks = std::make_shared< std::vector<callback_t> >();
    new_callbacks->reserve(callbacks->size() + 1);
    *new_callbacks = callbacks;
    new_callbacks->push_back(std::move(cb));

    atomic_store(&callbacks_, new_callbacks);
}

void invoke(event_t const& evt)
{
    auto callbacks = atomic_load(&callbacks_);

    // many people wrap each callback invocation into a try-catch
    // and de-register on exception
    for(auto& cb: *callbacks) (*cb)(evt); 
}

Specifically on the subject of asynchronous behavior when callback is executed while being de-registered, well here the best approach to take is remember of the Separation of Concerns principle.特别是在取消注册时执行回调时的异步行为主题,这里最好的方法是记住关注点分离原则。

The callback should not be able to die until it has been executed.回调在执行之前不应该死掉。 This is achieved via another classic trick called "extra level of indirection".这是通过另一个称为“额外间接级别”的经典技巧来实现的。 Namely, instead of registering user provided callback one would wrap it to something like the below and callback de-registration apart from updating the vector will call the below defined discharge() method on the callback wrapper and will even notify the caller of de-registration method of whether the callback execution finished successfully.也就是说,不是注册用户提供的回调,而是将其包装为如下所示的内容,并且除了更新向量之外,回调注销将调用回调包装器上定义的discharge()方法,甚至会通知取消注册的调用者回调执行是否成功完成的方法。

template <class CB> struct cb_wrapper
{
    mutable std::atomic<bool> done_;
    CB cb_;
    cb_wrapper(CB&& cb): cb(std::move(cb_)) {}

    bool discharge()
    {
        bool not_done = false;
        return done_.compare_exchange_strong(not_done, true);
    }

    void operator()(event_t const&)
    {
        if (discharge())
        {
            cb();
        }
    }

};

I can't see a right thing here.我在这里看不到正确的东西。 From your update I can see a problem: you are not synchronizing the invoke method with callback removal.从您的更新中,我可以看到一个问题:您没有将调用方法与回调删除同步。 There's an atomic but it's not enough.有一个原子,但这还不够。 Example: just after this line of code:示例:就在这行代码之后:

if (!_cancelled)

Another thread calls the remove method.另一个线程调用 remove 方法。 What can happen is that the onEvent() is called anyway, even if the removed method has removed the callback from the list and returned the result, there's nothing to keep synchronized this execution flow.可能发生的情况是onEvent()无论如何都会被调用,即使删除的方法已经从列表中删除了回调并返回了结果,也没有什么可以保持同步这个执行流程。 Same problem for the answer of @bobah. @bobah 的回答也有同样的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM