Join Internals

Architecture

Join's internal design consists of 2 parts:
  1. Low Level Resource Acquisition Core
The core of Join (and similarly JoCaml/Cw/C#.Joins) is a simple resource acquisiton (and contention management) manager which supports atomic acquisition of multiple resources:
whenever a new resource becomes available, the global status bitmap is updated, and the new global status bitmap is compared with chords' bitmaps; if a chord's bitmap matches (covered by) the global status bitmap, this acquisition can be satisfied (or chord is fired): one item is removed from each resource marked by the bits of chord's bitmap, and the global status bitmap is updated to reflect new status of resource availability.
If more than one chord can be fired, we can apply various scheduling algorithms: round robin, priority based
  1. High Level Messaging Semantics
On top of Join's core resource-acquisition manager, Join (Jocaml/Cw/C#.Joins) adds the following high level messaging based semantics :
Two types of port give different semantics :
If there exist a chord (acquisition pattern) which can fire, depending on the types of acquisition pattern (chord), there are 2 behaviours:

Some facts result from the architecture

  1. Join's usage of low level synchronization primitives
The rule to get the number of (mutex, condition variable) used by a Join class is simple:
Although Join's primitives (async<> / synch<> / chord) are high level, the author's experience has shown that concurrent applications written with Join usually use almost the same number of low level synchronization primitives as manually crafted ones.

For example, a manually crafted thread safe message queue will use a mutex and a condition variable to provide an asynchronous send/push interface (send and go) and a synchronous receive/pop interface (blocking wait if no message available).
In Join, a simple thread safe message queue can be defined as following:
template <typename msg_type>
class message_queue : public joint {
    public:
       async<msg_type> send;
       synch<msg_type,void> recv;
       message_queue() {
            chord(recv, send, &message_queue::forward);
       }
    private:
       msg_type forward(void_t r, msg_type s) {
            return s;
       }
};
Based on the above rule, since this message_queue class inherits from joint class and has one synch<> port, it will use one mutex and one condition variable, using exactly the same number of low level synchronization primitives as manually crafted.      
  1. Join's expressiveness
Join provides a generic framework to compose asynchronous and synchronous behaviours in thread safe manner. Join's special internal design makes it very expressive in creating concurrent applications:
  1. Since Join's core directly supports atomic acquisition of multiple resources, it helps nicely multithreaded applications which involve multiple resources:
Join provides a natural solution to this issue. We can use async ports/ports to represent the availability status of resources. The existence of messages at these async ports represent that resources (locks) are available to be consumed. Each acquisition pattern is represented as a chord which has one synch port and multiple async ports. For example, to resolve the conflicting acquisitions of resources from multiple threads, we can define the following resource manager:
class resource_manager : joint {
public:
    async<void> resource1;
    async<void> resource2;
    async<void> resource3;
    async<void> resource4;
    ......
    synch<void,void> acquire1;
    synch<void,void> acquire2;
    synch<void,void> acquire3;
    ......
    resource_manager() {
       chord(acquire1, resource1, resource3, resource4, &resource_manager::handle_acquire1);
       chord(acquire2, resource3, resource2, resource1, &resource_manager::handle_acquire2);
       chord(acquire3, resource2, resource4, &resource_manager::handle_acquire3);
       ......
       resource1();
       resource2();
       resource3();
       ......
    }
}
The above 3 chords define 3 different acquisition patterns competing for the same set of resources. Initially a message will be sent on each of these async ports to mark resource1,2,3 are available. Different threads will call acquire1() / acquire2() / acquire3(), block waiting until all of its required resources are available. When a chord is fired in one thread, that thread will remove a messages from each of async ports marked in the chord pattern, meaning it consumes these resources. When this thread is finished with using these resources, it will call these async ports to mark the resources available again. Please note that the order of these async ports (resources) defined in chords are not relevant anymore; since all required async ports (resources) are acquired atomically.