Futures in microcontrollers

Futures provide a simple interface for synchronizing asynchronous systems. This post introduces the basics of how they work.

One example is:

template<class F>
uint8_t use_future( F& f )
{
  if( !f.is_ready() )
  {
    f.wait();
  }
  return f.get();
}

What are futures

With C++11 a class named future was added to the STL. It provides an interesting concept which greatly simplifies some tasks in parallel programming.

The future supports two basic actions

  1. wait() - to wait until the value is ready,
  2. T get() - to obtain the value.

Since no value appears of nowhere, there needs to be a counterpart to the future. One is the promise which supports the operation set_value(T value) to set the value and notify the corresponding future.

An example of how to use them would be this:

Globally:

std::promise<int> p;
std::future<int> f = p.get_future();

Thread 1:

...
p.set_value( x );
...

Thread 2:

...
f.wait();
int y = f.get();
...

Now, the code in thread 2 doesn't need to know the details about how where and how that value gets set or how to communicated. No worrying about mutexes. It just uses the default future interface. Likewise, the code in thread 1 just uses the promise interface and is done with it.

In the following we do not differentiate between futures and promises. They are implemented in the same class.

The methods are:

  • accepts_value() - true if a value can be set,
  • set_value( T value ) - set the value,
  • is_ready() - true if value is set,
  • wait() - wait until the value is ready,
  • T get() - obtain the value (and to reset the status, can be called only once).

Why in microcontrollers

Where would one generally use futures?

  • In multithreaded environments.

What are microcontrollers generally?

  • Single threaded,

but

  • with interrupts!

When would one use futures?

  • When the result is not ready at the time you know that you might need it.

One abstract example would be a loop which manages an interactive user interface and an interrupt routine which manages a measurement with a long duration:

bool do_measurement = false;
int sum = 0;
int num_measurements = 0;
future<int> measurement_future;

// The interrupt service routine is called by a hardware interrupt.
interrupt1() {
    if( do_measurement && !measurement_future.is_ready() ) {

        // Read a new value and sum up.
        sum += read_sensor();
        num_measurements++;

        if( num_measurements > 1000 ) {

            // Set the value.
            measurement_future.set_value( sum );

            sum = 0;
            num_measurements = 0;
            do_measurement = false;
        }
    }
}

// The loop function is called repeatedly.
void loop() {

    ... do interactive stuff ...

    if( button_pressed ) {

        // Start the measurement.
        do_measurement = true;

    }

    if( measurement_future.is_ready() ) {

        // Display the mesurement result.
        display_value( measurement_future.get() );

        // Reset the future for the next measurement.
        measurement_future.reset();

    }

}

In this simple example the future does not save too much trouble compared to a more direct implementation. The strength of the futures, however, lies in the abstraction with an easy interface which doesn't change when the actual implementation gets more complex.

This simple interface can also be used by other functions. For example a function can be written like this:

void display_when_ready( future<int> fut )
{
    fut.wait();
    int value = fut.get();

    ... display the value ...
}

Now, if we change the underlying implementation we don't actually need to change the function.

But let's think about how one would implement such a future construct.

Simple implementation

The future needs to store the data and a status and provide the interface.

This would be a possible implementation:

/* A simple implementation of a future - no virtual inheritance.
 * The template parameter T determines the type of the value.
 * Optionally the functions on_wait and on_error can be specified.
 */
template< class T, void(on_wait)()=do_nothing, void(on_error)()=do_nothing >
class future_s
{
  // An enum for the possible states.
  // Empty when created empty or value is read via get().
  // Value_set after the value is set.
  enum class e_status {
    empty,
    value_set,
  };

  // The status.
  volatile e_status _status;

  // The actual value.
  volatile T _value;

public:

  // Start empty.
  future_s() : _status(e_status::value_set) {}

  // Start with value.
  future_s( T value ) : _status(e_status::empty), _value(value) {}

  bool accepts_value()
  {
    // Check the status.
    return _status == e_status::empty;
  }

  bool is_ready()
  {
    // Check the status.
    return _status == e_status::value_set;
  }

  // Read the value and reset the status back to empty.
  T get()
  {
    // Sanity check the status.
    if( _status != e_status::value_set ) {
      // Signal an error.
      on_error();
    }

    // Avoid race condition, copy the value.
    T temp = _value;

    // Reset status.
    _status = e_status::empty;
    return temp;
  }

  // Set the value and set the status to value_set.
  void set_value( T value )
  {
    if( _status != e_status::empty ) {
      // Signal an error.
      on_error();
    }

    // Set value and change status.
    _value = value;
    _status = e_status::value_set;
  }

  // Loop until the value is set.
  void wait()
  {
    // Wait until ready.
    while( !is_ready() ) {
      // Idle, optionally do something while waiting e.g. "sleep".
      on_wait();
    }
  }

};

the helper function

void do_nothing() {}

does nothing. It can be replaced e.g. with a function calling the sleep instruction for on_wait() or some error handler for on_error() to detect errors.

The costs of virtual inheritance

In order to be able to mix different future implementations it is useful to have them all inherit from a virtual base class defining the interface. In this case this would be

// Interface of the future.
template< class T >
class future
{
public:
  virtual bool is_ready() = 0;
  virtual T get() = 0;
  virtual void wait() = 0;
};

for the future and for the promise

// Interface of the promise.
template< class T >
class promise
{
public:
  virtual bool accepts_value() = 0;
  virtual void set_value( T&& ) = 0;
};

with a changed declaration of future_s

template< class T, void(on_wait)()=do_nothing, void(on_error)()=do_nothing >
class future_s : public future<T>, public promise<T>;

which now inherits form the interfaces.

This enables to use the future_s as

// The future itself.
future_s<uint8_t> f;

// Future interface.
future<uint8_t> &fi = f;

// Promise interface.
promise<uint8_t> &pi = f;

.

Both can be used to call functions which use the interface for example

template<class F>
uint8_t use_future( F& f )
{
  if( !f.is_ready() )
  {
    f.wait();
  }

  return f.get();
}

template<class P>
void use_promise( P& p )
{
  if( p.accepts_value() )
  {
    p.set_value(10);
  }
}

have the type of the future as template parameter and accept future_s as well as the base class future or promise, respectively.

Now, the interesting part is how much does it cost?

I compiled the attached Arduino sketch with the GCC 4.8.1 included in IDE 1.6.5 and get as a base line

  • 468 Bytes without any additional code,
  • 524 Bytes with the code implemented directly without future_s object,

and with the future objects

  • 518 Bytes with a future_s object without inheritance,
  • 590 Bytes with the functions using the future_s object,
  • 698 Bytes with a future_v object with inheritance,
  • 820 Bytes with the functions using the future and promise objects.

So, using a direct implementation without future costs 56 bytes and a future adds an additional 66 Bytes without virtual inheritance and with virtual inhertance 296 Bytes.

Compared to the 32 kBytes of flash of the ATMega328, these values look very acceptable for the convenience that is obtained.

Library and Examples

For easy use, I have make an Arduino-library which implements the code.

Download the library here.

To use the library

#include <future.h>

.

Three examples are included in the library. One on using future_s, one on using future_v (notice the difference in the binary size) and one using future_s in an interrupt for synchronization.

Chapter conclusion

The presented implementation is small enough to easily fit on a microcontroller and using the abstractions and allows easy synchronization without in-depth knowledge of the underlying data structures.