[英]How to handle thread-safe callback registration and execution in C++?
例如,我有一个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 ...
}
}
代码应该以下列方式是线程安全的:
EventGenerator::threadMainTask
EventGenerator::AddHandler
和EventGenerator::RemoveHandler
API。为了支持这一点,我有以下同步(请参阅代码中的注释):
[1]
是保护向量_handlers
免受多线程访问的互斥锁。[2]
和[3]
保护同时添加或删除处理程序。[4]
阻止在主线程发布事件时更改向量。 这段代码一直工作到...如果由于某种原因,在IEventHandler::onEvent(...)
的执行过程中,代码试图调用EventManager::RemoveHandler
或EventManager::AddHandler
。 结果是运行时异常。
以线程安全的方式处理事件处理程序注册和执行事件处理程序回调的最佳方法是什么?
>> 更新 <<
因此,根据输入,我已更新为以下设计:
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 ...
}
}
如您所见,还有一个额外的类EventDelegate
有两个目的:
在threadMainTask
,我使用std::vector<std::shared_ptr<EventDelegate>>
的本地副本,并且在调用回调之前释放锁。 这种方法解决了在IEventHandler::onEvent(...)
期间IEventHandler::onEvent(...)
EventGenerator::{AddHandler,RemoveHanler}
的问题。
对新设计有什么想法吗?
在 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);
}
特别是在取消注册时执行回调时的异步行为主题,这里最好的方法是记住关注点分离原则。
回调在执行之前不应该死掉。 这是通过另一个称为“额外间接级别”的经典技巧来实现的。 也就是说,不是注册用户提供的回调,而是将其包装为如下所示的内容,并且除了更新向量之外,回调注销将调用回调包装器上定义的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();
}
}
};
我在这里看不到正确的东西。 从您的更新中,我可以看到一个问题:您没有将调用方法与回调删除同步。 有一个原子,但这还不够。 示例:就在这行代码之后:
if (!_cancelled)
另一个线程调用 remove 方法。 可能发生的情况是onEvent()
无论如何都会被调用,即使删除的方法已经从列表中删除了回调并返回了结果,也没有什么可以保持同步这个执行流程。 @bobah 的回答也有同样的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.