Using LCOs

Extended Facilities for Futures

Lightweight Control Objects provide synchronization for HPX applications. Most of them are familiar from other frameworks, but a few of them work in slightly special different ways adapted to HPX.

  1. future
  2. queue
  3. object_semaphore
  4. barrier

Channels combine communication (the exchange of a value) with synchronization (guaranteeing that two calculations (tasks) are in a known state). A channel can transport any number of values of a given type from a sender to a receiver:

hpx::lcos::local::channel<int> c;
cout << c.get();      // will print '42'

Channels can be handed to another thread (or in case of channel components, to other localities), thus establishing a communication channel between two independent places in the program:

void do_something(
    hpx::lcos::local::receive_channel<int> c,
    hpx::lcos::local::send_channel<> done)
    cout << c.get();        // prints 42
    done.set();             // signal back

    hpx::lcos::local::channel<int> c;
    hpx::lcos::local::channel<> done;

    hpx::apply(&do_something, c, done);

    c.set(42);              // send some value
    done.get();             // wait for thread to be done

A channel component is created on one locality and can be send to another locality using an action. This example also demonstrates how a channel can be used as a range of values:

// channel components need to be registered for each used type (not needed
// for hpx::lcos::local::channel)

void some_action(hpx::lcos::channel<double> c)
    for (double d : c)
        hpx::cout << d << std::endl;

    // create the channel on this locality
    hpx::lcos::channel<double> c(hpx::find_here());

    // pass the channel to a (possibly remote invoked) action
    hpx::apply(some_action(), hpx::find_here(), c);

    // send some values to the receiver
    std::vector<double> v = { 1.2, 3.4, 5.0 };
    for (double d : v)

    // explicitly close the communication channel (implicit at destruction)
Composable Guards

Composable guards operate in a manner similar to locks, but are applied only to asynchronous functions. The guard (or guards) is automatically locked at the beginning of a specified task and automatically unlocked at the end. Because guards are never added to an existing task's execution context, the calling of guards is freely composable and can never deadlock.

To call an application with a single guard, simply declare the guard and call run_guarded() with a function (task).

hpx::lcos::local::guard gu;

If a single method needs to run with multiple guards, use a guard set.

boost::shared<hpx::lcos::local::guard> gu1(new hpx::lcos::local::guard());
boost::shared<hpx::lcos::local::guard> gu2(new hpx::lcos::local::guard());

Guards use two atomic operations (which are not called repeatedly) to manage what they do, so overhead should be extremely low.

  1. conditional_trigger
  2. counting_semaphore
  3. dataflow
  4. event
  5. mutex
  6. once
  7. recursive_mutex
  8. spinlock
  9. spinlock_no_backoff
  10. trigger