Document: WG14 N1196
Author: Lawrence Crowl
Date: 2006/10/23
Lawrence Crowl
Standard support for multi-threading is a pressing need.
The C++ approach is to standardize the current environment.
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.
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?
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.
Initialization and destruction of static-duration variables is tricky.
This problem does not exist in C.
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.
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();
}
}
};
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 may be built on the above primitives.
The committee has concerns that these facilities are not adequately field tests.