Introduction

Notifiers make possible anonymous communication between
objects in a system. Because they are anonymous, objects communicating have no
knowledge of one another and therefore are independent of the object they are
communicating with. They are also easy to understand providing a seamless
migration as new developers are introduced to a project. Other languages
(notably smalltalk) have this feature built in; C++ gives users the freedom to
create their own.

Design

Those interested in sending messages to subscribers do so
through the notifier class methods; those interested in receiving messages
implement the subscriber interface and register with the appropriate notifier.
The notifier is responsible for message routing; for each type of message,
there exists a different notifier. Notifier makes it possible to uncouple
senders and receivers (subscribers) of messages.

Figure 1. Notifier collaboration diagram

A thread-safe notifier

The notifier must fulfill three requirements:

simple

extensible

thread-safe

Simplicity is a vague term. I choose to define it in
terms of complexity – the less complex a piece of code is, the easier it is to
maintain, explain, optimize and test. The less complex a piece of code is, the
simpler it is. Convenient? ; )

Extensibility is a difficult metric to gauge – although
there are many definitions, the common theme amongst them is the ability to add
and incorporate new functionality, or technical advances, into an existing code
base. My ideal notion of extensibility is code that allows me to maintain it
over time without major work to the original code. Templates make this
possible by parameterizing key types and allowing one to work with those
outside of the implementation.

Thread-safety relates to the fact that clients
using a notifier in a multithreaded environment should not have to be concerned
with thread synchronization issues – subscribers will need to, but the notifier
should not.

The notifier has the following responsibilities:

registering subscribers

unregistering subscribers

notifying subscribers

To do so, a subscriber map is maintained. The subscriber
serves as the key, a flag indicating whether or not the subscriber is registered
serves as the data. A flag is used so that unregistration does not affect
(remove an entry from) the subscription map. This is important if subscribers
choose to unsubscribe during synchronous notification.

Upon registration the subscriber is inserted into the
subscriber map; if it already exists, a check is made to determine whether or
not it has been unregistered. If the subscriber has been flagged for
unregistration but has not been removed from the subscriber map, its state is
reset to 'registered'.

Notification involves obtaining a lock to the critical
section, iterating through the map and notifying the subscribers. Any
subscriber flagged as unregistered will not be notified. Once subscribers have
been notified, all subscribers flagged as unregistered from the subscriber map
are removed.

Unregistration involves flagging the subscriber for
unregistration. The next time notification occurs, the subscriber will be
removed from the map.

Asynchronous notification

Asynchronous notification is straightforward using the
thread pool created in
Windows Thread Pooling. A work unit is defined who, while processing, notifies
subscribers of the event.

If the service is to shutdown cleanly, we need a way of
signaling the waiting socket to shutdown. Moreover, there were several objects
that required notification of the service shutdown. Notifiers turned out to be
the best solution as we could quickly bind them to the shutdown event.

To receive notification of events your subscriber must subscribe using the
subscribe method.

void work::process()
{
// we subscribe in process because// we are created during notification. if// we were to subscribe during work::work// we would be added to the notifier map// and spawning would never cease to end!
subscribe();

To discontinue notification of events your subscriber must
unsubscribe using the unsubscribe method.

To asynchronously notify subscribers, create and queue an instance of async_notifier.

// and then kill it asynchronouslyglobal::thread_pool::instance().queue_request(new async_notifier<events>(die));

About the demo project

The demo project models the scenario above. Workers are
created who perform work until notified of shutdown. Upon notification, the
workers die and the system gracefully shuts down. More workers may be spawned
to simulate a heavy load on the code.

The thread pool is initialized.

Work is queued and is killed asynchronously using the notifier.

A spawn work unit and an input work unit are queued.

The main thread waits on an event signaled when the input work unit dies.

There are some important considerations to keep in mind
when going through the testbed. Our main thread waits on an event that is
signaled when input is dying. Depending on the amount of work pending, exiting
the application immediately could be disastrous.

To understand, consider the following scenario:
the user has instructed input to die; input synchronously
notifies all subscribers to die and signals our main thread to die.

For the sake of argument, assume there are 100 work units
queued and pending. Threads in the thread pool will not die until all work is
extinguished and they have received the shutdown flag. Meanwhile, main is unwinding
and destroys the process heap. Our work units are finally released as the last bit of
work trickles out. Without a heap, what happens?

To keep things simple, I pause the main thread during
which the machinery gently shuts down. More elaborate synchronization
mechanisms may be employed. However, during shutdown I am not encumbered by
performance requirements and prefer the simple implementation. Your mileage
may vary.

Conclusions

Notifiers are a great way of coupling parts of a system
that are interested in state changes but don't want to be bound to one
another. Templates provide a natural mechanism for reuse of the notification
subsystem with different state data. Synchronous and asynchronous notification
provides the flexibility required for all but the most esoteric situations.

Whether you choose to use a freely available
implementation or to roll your own, notifiers make building complex solutions
in any language more manageable.
Happy Coding!

History

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

Joshua Emele lives in San Francisco. A member of Plugware Solutions, Ltd. and specializes in network, database, and workflow applications in c++.
He is madly in love with life and his partner and enjoys teaching, playing classical guitar,
hiking, and digital electronics.

Comments and Discussions

There seems to be something missing in the instructions. I had to look at the code to realize that I needed to

global::thread_pool::instance().initialize();

However, this still does not work. I'm not a dll-expert so it may be my fault. I have the class that fires the events inside a dll. I have the subscriber in an exe that uses the dll. Unfortunately they seem to be using different "instance"s of m_c_subscriber in the dll and the exe. The sender of the event thinks that no one has subscribed and nothing gets sent.