Publisher/Subscriber with changing subscriptions during loop
This is more of a general design query I had. I have implemented a publish / subscribe pattern by maintaining a list of subscribers. When an event to publish occurs, I loop through the subscribers and push the event to each one, of them in turn.
My problem occurs when due to that publication, somewhere in the depth of the software, another component or event the described component decide to unsubscribe themselves. By doing so, they invalidate my iterator and cause crashes.
What is the best way to solve this? I have been thinking of wrapping the whole publication loop into a try catch block, but that means some subscribers miss the particular subscription upon which someone unsubscribed, and seems a bit over the top. Then I tried feeding it back, e.g. I turned the void publish call into a bool publish call that returns true when the subscriber wants to be deleted, which works for that case, but not if another subscriber unsubscribes. Then I am thinking to "cache" unsubscription requests somewhere and release them when the loop is done, but that seems a bit ov开发者_如何学Cerkill. Then I am thinking of storing the iterator as a class member, so that I can manipulate the iterator from outside, but that gets messy (say you unsubscribe subscriber 1, iterator is pointed at 2, and the container is a vector - then the iterator would have to be decremented). I think I might prefer one of the latter two solutions, but both seem not ideal.
Is this a common problem? Is there a more elegant solution?
You could either disallow subscription operations during publication, or you could use an appropriate data structure to hold your subscription list, or both.
Assuming that you keep your subscribers in a std::list
, you could run your loop thus:
for(iterator_type it = subs.begin(); it != subs.end(); ) {
iterator_type next = it;
++next;
it->notifier();
it = next;
}
That way, if the current item is removed, you still have a valid iterator in next
. Of course, you still can't allow arbitrary removal (what if next
is removed?) during publication.
To allow arbitrary removal, mark an item as invalid and defer its list removal until it is safe to do so:
... 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;
elsewhere,
... removal code:
if(dontRemoveItems) item->valid = false;
else subs.erase(item);
精彩评论