C++ Mutable Threads

ISO/IEC JTC1 SC22 WG21 N3356 = 12-0046 - 2012-01-15

Chris Mysen, mysen@google.com

Introduction
Solution
    Thread Local Variables
    Mutable Thread Operations
    Example Usage
Synopsis

Introduction

The standard thread library provides a thread that executes a single function, and terminates when that function returns. If a sequence of distinct operations is to be performed, each represented by a single function, it is normally inefficient to create a new thread object for each function. We desire an implementation that allows a thread to be re-used to perform additional work.

Solution

We propose a new thread class, mutable_thread, with a similar interface to the standard thread, but with the capability of adding additional executable functions. (Functions executed by a mutable_thread are referred to as "work units" in this document.)

A mutable_thread may be in a running state, when it is executing a work unit, or it may be in a stopped state if the current work unit has terminated. If the thread is in a stopped state, it may be give a new work unit to execute.

The initial work unit may be set in the constructor. Subsequent work units may be added using the execute() or try_execute() functions.

If the mutable_thread is currently stopped, the execute() function will cause it to begin running the given work unit immediately. If not, it will block until the current work unit terminates. The try_execute() function is non-blocking, and returns a boolean indicating if the new work unit can be executed. Hence try_execute() allows execute() to be called without blocking (acting as a single entry queue). This behavior allows continuations to be created which can do cleanup work or other continuations without blocking the calling thread.

As a thread can be re-used by many different callers, it becomes necessary to be able to query the state of a mutable_thread (which can be in several states, including idle, running, waiting to join, or stopped). See is_joining() and is_done() below.

Thread Local Variables

Thread local variables could have a different meaning when threads are mutable as the function under execution can change over time whereas thread local variables persist for the life of the thread. The mutable_thread class assumes thread-lifetime semantics instead of function-execution lifetime (so a thread local variable persists as long as the thread is not joined).

Mutable Thread Operations

constructor();

constructor( function<void()> );

Parameter is the initial function to execute on the thread. Execution begins immediately.

mutable_thread& operator=( mutable_thread&& other );

Has the same semantics as std::thread::operator=(). Transfers thread execution state to the given mutable_thread object. Will call terminate if the current thread is currently in the joinable state.

bool joinable();

Indicates if the current thread is actively executing a work unit. (get_id() != id() is not necessarily true if this returns true).

thread::id get_id() const;

This has the same semantics as std::thread::get_id().

native_handle_type native_handle();

This has the same semantics as std::thread::native_handle().

void join();

Block waiting for the thread to finish executing. This also places the thread into a joining state, which means that it will no longer accept additional work. Throws std::system_error if joinable() == false.

bool try_execute( function<void()> );

Non-blocking execution function which starts a new function executing in the thread if the thread is not currently executing work and immediately returns. Returns false if execution could not start.

bool execute( function<void()> );

Blocking version of try_execute, blocks the caller until the thread completes execution of its running work unit.

void swap( mutable_thread& other );

Exchanges the underlying work with the thread other. This will swap the active threads as well as pending work.

bool is_joining();

Returns true if the callers are attempting to shut down the thread (if join() has been called).

bool is_done();

Returns true if the thread has stopped.

There are no copy or assignment operations.

Example Usage

The mutable_thread can be used to implement simple concepts like a thread pool with the ability of the thread to do things upon of other tasks. This also shows that you can easly manage threads without only loose ownership of the threads.


for (size_t i = 0; i < min_threads; ++i) {
  pool1.add(new mutable_thread());
}

// Get a thread and start it running.
mutable_thread* next_thread = pool1.get_from_pool();
next_thread->execute(bind(&f, 10));

// This enqueues a single unit of work which will move the thread to pool2 when
// the thread completes its previous work.
next_thread->execute(bind(&return_to_pool, &pool2, next_thread));

// Remove threads from the pool and shut them down.
while (pool1.has_threads()) {
  mutable_thread* t = pool1.get_from_pool();
  t->join();
}

Synopsis


class mutable_thread {
 public:
  // Create and empty thread which can later be started with the execute() function.
  mutable_thread();

  // Create a mutable_thread with a function 
  template<class F>
  explicit mutable_thread(F f);

  ~mutable_thread();


  // Thread join. Will not complete joining until all queued work is completed
  // (makes no guarantees that the thread will terminate).
  void join();

  // Setup function for execution if there isn't currently something executing
  // or if there is only a single task currently executing.
  // Return false if thread is currently doing other work.
  template<class F>
  bool try_execute(F f);

  // Like try_execute, but blocks until there is an empty spot to queue up for
  // execution.
  // Return false if the thread is in the process of joining (and thus cannot
  // accept new work).
  template<class F>
  bool execute(F f);

  // Join has been called but the thread is still executing.
  bool is_joining();

  // Thread has fully joined and will not accept any more work.
  bool is_done();
};