How to Use Condition Variables by Gene Cooperman (gene@ccs.neu.edu) Copyright (c) 2021 -- all rights reserved Simple pattern for condition variables: [ See diagram in acquire-release.pdf in this directory. https://www.ccs.neu.edu/course/cs3650/parent/thread-synch/acquire-release.pdf ] A. Acquire resource pthread_mutex_lock(&guard_mutex); // Update some guard variables (some state variables) // For example, update the number of threads that are waiting. while (safety_condition(guard_vars) == False) { pthread_cond_wait(&cond, &guard_mutex); } // Update some guard variables (some state variables) // For example, update number of threads that are using resource // and decrement the number of threads that are waiting. pthread_mutex_unlock(&guard_mutex); B. Use resource (If we acquired a read resource, we should be guaranteed that any writers are waiting. If we acquired a write resource, we should be guaranteed that any readers _and_ other writers are waiting. C. Release resource pthread_mutex_lock(&guard_mutex); // This can be replaced by pthread_cond_signal for optimizing // the performance, but pthread_cond_broadcast() // is always safe. pthread_cond_broadcast(&cond); // Update some guard variables (some state variables) // For example, update number of threads that are using resource. pthread_mutex_unlock(&guard_mutex); COMMENT: We can never deadlock when using guard variables in a critical section for a mutex. So, the only danger is: a. deadlock: if we have bad logic in the safety_condition() or in updating the guard/state variables: For example, if a writer is waiting, but the only readers in the system are also waiting. b. safety: if we have bad logic in the safety_condition() or in updating the guard/state variables: For example, if two writers are able to acquire the write resource at the same time. COMMENTS IN DEPTH: Variations that are more specialized -- for greater performance: [ Mostly in class, we care about correctness only. The concepts here are only for greater performance. Unless specified in an assignment, you don't need to use these optimizations. ] Declare two condition variables: cond_readers and cond_writers Now, if you want to wake up the writers first (writer preference), just do: pthread_cond_broadcast(&cond_writers); pthread_cond_broadcast(&cond_readers); or maybe for even greater performance: // Add 'num_writers_waiting' to your state variables, and: if (num_writers_waiting > 0) { // We only need to wake up one writer right now. pthread_cond_signal(&cond_writers); } else { pthread_cond_broadcast(&cond_readers); } IMPORTANT TRAP: A condition variable is not a semaphore! Don't try to use a condition variable where what you really want is a semaphore. It's tempting to write: SEM_WAIT: pthread_mutex_lock(guard_vars); pthread_cond_wait(&cond, &guard_mutex); pthread_mutex_unlock(guard_vars); SEM_POST: pthread_mutex_lock(guard_vars); pthread_cond_signal(&cond); pthread_mutex_unlock(guard_vars); Either of two bad things can happen here. (a) spurious wakeup: the system sends a random signal (e.g., SIGWINCH) to the waiter. The waiter wakes up without any post. (b) lost wakeup: the poster posts first. Then the waiter begins to wait. It never saw the post. Either directly use sem_wait() and sem_post() (preferred) or implement your own semaphore using condition variables: (Yuck!) SEM_WAIT: pthread_mutex_lock(guard_vars); count--; while (count < 0) { pthread_cond_wait(&cond, &guard_mutex); } pthread_mutex_unlock(guard_vars); SEM_POST: pthread_mutex_lock(guard_vars); count++; pthread_cond_signal(&cond); pthread_mutex_unlock(guard_vars);