Document: WG14 N1196
Author: Lawrence Crowl
Date: 2006/10/23

C++ Threads

Lawrence Crowl

Google

Introduction

Standard support for multi-threading is a pressing need.

The C++ approach is to standardize the current environment.

Memory Model

Presuming that all writes are instantly available to all threads is not viable.

The standards adopts a message memory model.

Sequencing has been redefined.

Sequencing has been extended to concurrency.

But what is a location?

Optimizers are not unaffected.

Loops without synchronization may be assumed to terminate.

Atomic Types and Operations

All threads observe the same sequence of values for an atomic type.

Atomic operations provide acquire, release, both, or neither.

Atomic types are structs, but could be primitive types.

The types are comprehensive over the important primitive types.

Atomics may be compiled by both languages.

atomic_flag v1 = ATOMIC_FLAG_INIT;
atomic_long v2 = { 1 };
atomic_void_pointer v3 = { 0 };
void func()
{
    if ( atomic_flag_test_set( & v1 ) )
        atomic_flag_clear( & v1 );
    long t = atomic_load_acquire( & v2 );
    atomic_compare_swap( & v2, t, t|1 );
    atomic_fetch_ior_ordered( & v2, 1 );
    atomic_fetch_add_ordered( & v3, 1 );
#ifdef __cplusplus
    long l1 = v2; v2 = 3; ++v2; v2 &= 7; v3 += 4;
#endif
}

Atomic operations must be lock-free to be used in signals.

Atomic operations must be address-free to be used between processes.

Sequential consistency is still not settled.

x and y are atomic and initially 0
thread 1: atomic_store( &x, 1 )
thread 2: atomic_store( &y, 1 )
thread 3: if ( atomic_load( &x ) == 1 && atomic_load( &y ) == 1 )
thread 4: if ( atomic_load( &y ) == 1 && atomic_load( &x ) == 1 )
Are both conditions exclusive?

Thread-Local Storage

At least 5 vendors already implement the proposed facility.

Define a new thread storage duration.

Storage is unique to each thread.

Addresses of thread variables are not constant.

Thread storage are accessible to other threads.

Dynamic Initialization and Destruction

Initialization and destruction of static-duration variables is tricky.

This problem does not exist in C.

Thread Semantic Model

Initiate a thread with a fork on a function call.

Join waits for the function to return.

Mutexes provide mutual exclusion.

Condition variables enable the monitor paradigm.

Thread termination is voluntary.

Thread scheduling is limited.

Current Thread Implementation Approach

The thread model is based on a full C++ library implementation.


std::thread::handle my_handle =
	std::thread::create( std::bind( my_func, 1, "two" ) );
other_work();
thread::join( my_handle );

Locks hold a mutex within a given scope.

class buffer
{
    int head, tail, store[10];
    std::thread::mutex_timed mutex;
    std::thread::condition not_full, not_empty;
public:
    buffer() : head( 0 ) , tail( 0 ) { }

    void insert( int arg )
    {
        std::timeout wake( 1, 0 );
        lock scoped( mutex );
        while ( (head+1)%10 == tail )
            if ( not_full.timed_wait( wake ) )
                throw "buffer full too long";
        store[head] = arg; head = (head+1)%10;
        not_empty.notify();
        }
    }
};

Rejected Thread Implementation Approach

The C++ committee rejected a syntax-based approach.

The approach provided new operators for fork and join.

int function( int argument )
{
    int join pending = fork work1( argument );
    // work1 continues concurrently
    int value = work2( argument );
    return value + join pending;
}

The rejected approach extended the set of control statements to manage synchronization.

struct buffer
{
    int head, tail, store[10];
    mutex_timed mutex;
    condition not_full, not_empty;
};

void buffer_insert( struct buffer *ptr; int arg )
{
    lock( ptr->mutex )
    {
        timeout wake = { 1, 0 };
        wait( ptr->not_full;
               (ptr->head+1)%10 != ptr->tail;
               wake )
        {
            ptr->store[ptr->head] = arg;
            ptr->head = (head+1)%10;
            notify( ptr->not_empty; 1 );
        }
        else
            failure( "buffer full too long" );
    }
    else
        failure( "too much buffer contention" );
};

Higher-Level Facilities

Higher-level facilities may be built on the above primitives.

The committee has concerns that these facilities are not adequately field tests.