/* * Pthread conditional variables * ============================= * * Simulation of the producer-consument(s) pattern, implemented using conditional * variables. * * *tags*: test, C99 * * Description * ----------- * * This program simulates the *producer-consument(s) pattern*, implemented using * conditional variables. Instead of spinning, consumers are sleeping * until some item is enqueued into the shared queue. * If compiled with `-DBUG`, condition is not re-checked after the * `pthread_cond_wait` returns. But this is obviously incorrect because * when a thread receives a signal it is not necessarily the first one that * gets scheduled when the signalling thread unlocks the mutex. * For example we could have the following series of operations: * * 1. *Cons_0* locks queue_mutex, sees that `enqueued == 0`, and waits (releases mutex). * 2. *Prod* locks queue_mutex, produces new item and signals about new item being added. * 3. *Cons_1* tries to lock `queue_mutex`, but fails and has to wait. * 4. *Prod* unclocks `queue_mutex` and the scheduler decides to schedule *Cons_1* instead of *Cons_0*. * 5. *Cons_1* locks `queue_mutex`, sees that `enqueued != 0`, and removes the item * (but note that *Prod* already signalled *Cons_0* that it should get this item). * 6. *Cons_1* unlocks queue_mutex and the scheduler resumes *Cons_0*. * 7. *Cons_0* now tries to remove from an empty queue. * * * * Thus at least two consumers and two items to process are needed to have * assertion violated in some run of the program. * * Current implementation of conditional variables in Pthread library * provided within DiVinE also takes into consideration that `pthread_cond_signal` * can send signal to more than one thread (as it is defined in the *POSIX* specifications). * However spurious wakeup is not yet implemented. * * Parameters * ---------- * * - `BUG`: if defined than the algorithm is incorrect and violates the safety property * - `NUM_OF_CONSUMERS`: the number of consumers * - `NUM_OF_ITEMS_TO_PROCESS`: the number of items to be processed by consumers * * Verification * ------------ * * - all available properties with the default values of parameters: * * $ divine compile --llvm pthread_cond_variables.c * $ divine verify -p assert pthread_cond_variables.bc -d * $ divine verify -p safety pthread_cond_variables.bc -d * * - playing with the parameters: * * $ divine compile --llvm --cflags="-DNUM_OF_CONSUMERS=4 -DNUM_OF_ITEMS_TO_PROCESS=8" pthread_cond_variables.c * $ divine verify -p assert pthread_cond_variables.bc -d * $ divine verify -p safety pthread_cond_variables.bc -d * * Execution * --------- * * $ clang -lpthread -o pthread_cond_variables.exe pthread_cond_variables.c * $ ./pthread_cond_variables.exe */ #include #include #include #include #include #include #ifndef NUM_OF_CONSUMERS #define NUM_OF_CONSUMERS 2 #endif #ifndef NUM_OF_ITEMS_TO_PROCESS #define NUM_OF_ITEMS_TO_PROCESS 2 #endif volatile int enqueued = 0; volatile int dequeued = 0; pthread_mutex_t queue_mutex; pthread_cond_t queue_emptiness_cv; void *consumer( void *arg ) { #ifndef __divine__ intptr_t id = ( intptr_t ) arg; #endif while ( 1 ) { pthread_mutex_lock( &queue_mutex ); #ifdef BUG if ( dequeued < NUM_OF_ITEMS_TO_PROCESS && enqueued == 0 ) { #else // correct while ( dequeued < NUM_OF_ITEMS_TO_PROCESS && enqueued == 0 ) { #endif #ifndef __divine__ printf( "Consumer ID = %d is going to sleep.\n", id ); #endif pthread_cond_wait( &queue_emptiness_cv, &queue_mutex ); #ifndef __divine__ printf( "Consumer ID = %d was woken up.\n", id ); #endif } if ( dequeued == NUM_OF_ITEMS_TO_PROCESS ) { pthread_mutex_unlock( &queue_mutex ); break; } // dequeue assert( enqueued > 0 ); // fails if macro BUG is defined ++dequeued; --enqueued; #ifndef __divine__ printf( "Consumer ID = %d dequeued an item.\n", id ); #endif pthread_mutex_unlock( &queue_mutex ); // here the thread would do some work with dequeued item (in his local space) #ifndef __divine__ sleep( 1 ); #endif } return NULL; } int main( void ) { int i; pthread_t * threads = ( pthread_t * )( malloc( sizeof( pthread_t ) * NUM_OF_CONSUMERS ) ); if ( !threads ) return 1; // initialize mutex and condition variable objects pthread_mutex_init( &queue_mutex, NULL ); pthread_cond_init ( &queue_emptiness_cv, NULL ); // create and start all consumers for ( i=0; i