Inter-thread communication with Waker

The foundation for inter-thread communication in Stakker is the Waker mechanism. This uses an atomic bitmap-tree, which means that many wakeup events from other threads will be accumulated into a single I/O event on the Stakker thread, using only a small number of atomic operations to recover them, keeping the load on the Stakker thread low.

A Waker will normally be paired with a channel or some other shared mutable state, e.g. data within a Mutex. So the Waker is used to notify a handler in the Stakker thread that it needs to examine the shared state and respond to whatever it finds there.

PipedThread is an example of this, combining a thread, two message pipes and a Waker. This provides a convenient way to handle some very simple scenarios for running heavy or blocking calls in another thread. However it should be straightforward to build other inter-thread communication methods on top of Waker.

One thing to note is that when using channels such as crossbeam, ideally we'd want to only notify the Waker when the channel is empty at the instant that the message is added. However the channel APIs typically don't give us a way to detect this condition. (Perhaps it's not even possible to detect this in some cases due to how the channel is implemented.) Attempting to detect it with an is_empty() call on the channel before adding the message is doomed to intermittent failure due to races. So this means that another thread adding something to a channel for the Stakker thread to pick up must notify the Waker on every message sent. So Waker is designed to make this as cheap as possible. After the first time, it will take only one atomic operation.