Note! This project has moved to github.com/armmbed/mbed-events

Dependents:   SimpleHTTPExample

You are viewing an older revision! See the latest version

Composable Event Loops

At the simplest level, composable event loops combine the cheap synchronicity of event loops with the composability of preempted threads.

Multithreading

In a traditional OS, asynchronous tasks are generally accomplished by multiple threads or processes. Each thread has its own stack and is preempted periodically to switch between active threads. Low-level synchronization primitives or synchronized queues are often used to communicate between running threads.

Example of a multithreaded teapot:

https://docs.google.com/drawings/d/1aIN0wwYnO_eCx0fcRNlLk8SkuuiMGPGngCbNp49VZok/pub?w=610&h=356

Benefits:

  • Complicated logic can be executed linearly without worrying about delays
  • Multiple threads can be composed with minimal impacting to the timing of existing threads
  • Supports multiple cores
  • Parallelism is transparent to the user

Event Loop

In the classic event loop model, a single process executes short-lived events sequentially. Only a single stack is used and no synchronization is needed between events. The user is responsible for ensuring that events return in a lively manner.

Example of an event loop based teapot:

https://docs.google.com/drawings/d/1lPIfTdBmZdixWd_1HIVP-S7Lptu67Y8kqttALzl51hg/pub?w=610&h=356

Benefits:

  • Synchronization is elided
  • Reduced memory consumption with only a single stack
  • No cost for context switching and synchronization
  • Parallelism is explicit and controlled

Composable Event Loops

A composable event loop system is a model for structuring multitasking programs such that each module is contained in a single event loop. Multiple modules are separated into distinct parallel threads and can communicate through message passing in the form of registering events across modules.

Example of a teapot using composable event loops:

https://docs.google.com/drawings/d/1HYQ1hge6Z_FWHt4col6RU_E610DF5NlpjYS0SeAR8t4/pub?w=608&h=275

At minimum, composable event loops need three primitives:

- Modular event loops - dispatching of a module's queued events - Multithreading - isolation between multiple module's threads of execution - Synchronized event registration - message passing between modules in the form of enqueuing events

Example

An asynchronous teapot based on the simple events library:

/*  An asynchronous tea pot built on composable event loops
 */
class TeaPot {
private:
    // Internal event loop and set of events for the tea pot
    EventLoop loop;
    Event<void()> begin_event(&loop);
    Event<void(float)> read_event(&loop);
    
    // Other tea pot internals
    HeatingElement heater;
    AnalogIn sensor;
    FuncPtr<void()> done_callback;

public:
    // The publically-accessibly boil method can be called from other threads.
    // Registering with the internal event loop synchronizes the tea pot.
    void boil(FuncPtr<void()> callback) {
        done_callback = callback;

        begin_event.attach(this, &TeaPot::begin_callback);
        begin_event.trigger();
    }

private:
    // Event registered from the boil method in case the heating element is not thread-safe.
    void begin_callback() {
        heater.on();

        read_event.attach(this, &TeaPot::read_callback);
        sensor.read(&read_event);
    }

    // Event for handling read callbacks. This callback is issued from the AnalogIn sensor, 
    // but runs on the TeaPot's event loop. AnalogIn may or may-not use an event loop.
    void read_callback(float data) {
        // Hopefully this log call will be pretty quick. However, if the log call takes a while,
        // we only have to worry about it blocking our tea pot.
        log.log("got: %d\n", data);

        if (data > 110) {
            heater.off();

            // Since events are compatible function objects, triggering events that may be from
            // other event loops is as easy as a function call.
            if (done_callback) {
                done_callback();
            }
        } else {
            sensor.read(&read_event);
        }
    }
}

Benefits

The core benefit of composable event loops is the alignment of synchronization issues with the separation of concerns between individual modules. When creating a module, a developer needs to worry about external synchronization and internal timing.

Benefits:

  • Modules are internally synchronized, removing the need for synchronize individual components or worry about the interactions between internal threads
  • Multiple modules can be composed with minimal impact to the timing of existing modules
  • The quantity of stacks is statically bounded by the number of modules
  • Supports multiple cores bounded by the number of modules
  • Easily integrated with interrupts by treating them as another module boundary
  • Easily integrated with traditional multithreaded environments
  • Parallelism is transparent across module boundaries but controlled inside modules

All wikipages