循环期间具有更改订阅的发布服务器/订阅服务器

Publisher/Subscriber with changing subscriptions during loop

本文关键字:服务器 循环      更新时间:2023-10-16

这更像是我的一个通用设计查询。我通过维护订阅者列表实现了发布/订阅模式。当要发布的事件发生时,我循环遍历订阅者,并依次将事件推送给每个订阅者。

当由于该出版物、软件深处的某个地方、另一个组件或事件,所描述的组件决定自行取消订阅时,就会出现我的问题。这样做会使我的迭代器失效并导致崩溃。

解决这个问题的最佳方法是什么?我一直在考虑将整个发布循环包装成一个try-catch块,但这意味着一些订阅者错过了有人取消订阅的特定订阅,这似乎有点过头了。然后我尝试将其反馈,例如,我将void发布调用转换为bool发布调用,当订阅者想要删除时,该调用返回true,这在这种情况下有效,但如果另一个订阅者取消订阅,则无效。然后我想把取消订阅请求"缓存"在某个地方,并在循环完成后释放它们,但这似乎有点过头了。然后我考虑将迭代器存储为一个类成员,这样我就可以从外部操作迭代器,但这会变得很混乱(比如取消订阅订阅者1,迭代器指向2,容器是一个向量,然后迭代器必须递减)。我想我可能更喜欢后两种解决方案中的一种,但两者似乎都不理想。

这是常见问题吗?有没有更优雅的解决方案?

您可以在发布期间禁止订阅操作,也可以使用适当的数据结构来保存订阅列表,或者两者兼而有之。

假设您将订阅者保持在std::list中,则可以运行循环:

for(iterator_type it = subs.begin(); it != subs.end(); ) {
    iterator_type next = it;
    ++next;
    it->notifier();
    it = next;
}

这样,如果删除了当前项,那么next中仍然有一个有效的迭代器。当然,您仍然不允许在发布期间任意删除(如果删除了next呢?)。

要允许任意删除,请将项目标记为无效,并推迟删除其列表,直到安全为止:

... publication loop ...
dontRemoveItems = true;
for(iterator_type it = subs.begin(); it != subs.end(); ++it) {
    if(it->valid)
        it->notifier();
}
std::erase(std::remove_if(...,, IsNotValid),...);
dontRemoveItems = false;

其他地方,

... removal code:
if(dontRemoveItems) item->valid = false;
else subs.erase(item);