Condition variables via pthread Library in C
CSCI3150 Introduction to Operating Systems, Fall
2024
LI Jianqiang
[email protected]
Oct 3, 2024
Reminder
Assignment 1
Due at 18:00:00 p.m., Mon, Oct 7th
Assignment 2
Will release after tutorial, Oct 3th
Due at 18:00:00 p.m., Mon, Nov 4th
2
Overview
Tutorial this week
Pthread Library part 2
Key interfaces for condition variables
Condition variable usage in C
Multiple Producers and Consumers
Barrier Synchronization
Dining Philosophers Problem
All examples used in this tutorial:
https://github.com/henryhxu/CSCI3150/tree/
2024-Fall/tutorial/T05
3
Review: Pthread Library part 1
The Pthread (POSIX threads) library is a powerful tool for creating and
managing threads in C. It provides a standardized API to facilitate multi-
threading, which is essential for developing concurrent applications.
To use it: #include <pthread.h>
Key Interfaces:
pthread_create pthread_mutex_init
pthread_exit pthread_mutex_lock
pthread_join pthread_mutex_unlo
ck
pthread_mutex_dest
roy
4
Pthread Library part 2
The Pthread (POSIX threads) library is a powerful tool for creating and
managing threads in C. It provides a standardized API to facilitate multi-
threading, which is essential for developing concurrent applications.
To use it: #include <pthread.h>
Key Interfaces:
pthread_create pthread_mutex_init pthread_cond_init
pthread_exit pthread_mutex_lock pthread_cond_wait
pthread_join pthread_mutex_unlo pthread_cond_signal
ck pthread_cond_broad
pthread_mutex_dest cast
roy pthread_cond_destro
y 5
1. pthread_cond_init
Purpose: initialize a condition variable.
Syntax:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *at
tr);
Parameters:
• cond: pointer to the condition variable.
• attr: optional attributes for the condition variable (use NULL for default).
Example:
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
Another way: using PTHREAD_COND_INITIALIZER
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
6
2. pthread_cond_wait
Purpose: wait for a condition variable to be signaled, releasing
the associated mutex.
Syntax:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
Parameters:
• cond: pointer to the condition variable.
• mutex: pointer to an associated mutex (must be locked before calling).
Example:
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
7
2. pthread_cond_wait
Question: why a mutex will be passed into this function?
Let’s see what has been done
inside:
pthread_cond_wait(pthread_cond_t* C, pthread_mutex_t* M)
{
<put this thread into wakeup queue of condtion var C.>
pthread_mutex_unlock(M);
sleep();
pthread_mutex_lock(M);
<take this thread out of wakeup queue of condition var C.>
}
8
3. pthread_cond_signal
Purpose: wake up one thread waiting on a condition variable.
Syntax:
int pthread_cond_signal(pthread_cond_t *cond);
Parameters:
• cond: pointer to the condition variable.
Example:
pthread_mutex_lock(&mutex);
// Change the condition
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
9
4. pthread_cond_broadcast
Purpose: wake up all threads waiting on a condition variable.
Syntax:
int pthread_cond_broadcast(pthread_cond_t *cond);
Parameters:
• cond: pointer to the condition variable.
Example:
pthread_mutex_lock(&mutex);
// Change the condition
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
1
5. pthread_cond_destroy
Purpose: destroy a condition variable, releasing resources.
Syntax:
int pthread_cond_destroy(pthread_cond_t *cond);
Parameters:
• cond: pointer to the condition variable.
Example:
pthread_cond_destroy(&cond);
1
Example 1: Multiple Producers and Consumers
Problem: Manage multiple producer and consumer threads
accessing a shared buffer. Producers add items to the buffer,
while consumers remove them. The challenge is to synchronize
access and avoid race conditions.
Solution: Use a mutex to protect the buffer and condition
variables to signal when the buffer is not full (for producers) and
not empty (for consumers). Producers wait if the buffer is full, and
consumers wait if it's empty, ensuring safe and efficient access.
1
Example 1: Multiple Producers and Consumers
Requirements:
void* consumer(void* arg) { consumer wait when
• Producers wait if the buffer is full
while (1) {
empty
• Consumers wait if it's empty pthread_mutex_lock(&mutex);
#include <pthread.h> example while (count == 0) {
#include <stdio.h> pthread_cond_wait(&cond_consume, &mutex);
#include <stdlib.h>
1.c }
#define BUFFER_SIZE 10 int item = buffer[--count]; // Consume item
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; printf("Consumed, count: %d\n", count);
pthread_cond_t cond_produce = PTHREAD_COND_INITIALIZ pthread_cond_signal(&cond_produce);
ER; pthread_mutex_unlock(&mutex);
pthread_cond_t cond_consume = PTHREAD_COND_INITIALIZ } signal producer when not
ER; return NULL;
producer wait when full
int buffer[BUFFER_SIZE]; }
int count = 0; full int main() {
void* producer(void* arg) { pthread_t producers[2], consumers[2];
while (1) { for (int i = 0; i < 2; i++) {
pthread_mutex_lock(&mutex); pthread_create(&producers[i], NULL, producer, N
while (count == BUFFER_SIZE) { ULL);
pthread_cond_wait(&cond_produce, &mutex) pthread_create(&consumers[i], NULL, consumer, N
; ULL);
} }
buffer[count+ for (int i = 0; i < 2; i++) {
+] = rand() % 100; // Produce item
signal consumer when not pthread_join(producers[i], NULL);
printf("Produced, count: %d\n", count); pthread_join(consumers[i], NULL);
empty
pthread_cond_signal(&cond_consume); } 1
pthread_mutex_unlock(&mutex); return 0;
Example 2: Barrier Synchronization
Problem: In a barrier synchronization scenario, multiple threads
must wait at a specific point until all threads reach that point.
Only when all threads have reached the barrier can they all
proceed.
Solution: Each thread increases a counter when it reaches the
barrier. If not all threads have arrived, it waits. The last thread to
arrive signals all waiting threads to continue.
1
Example 2: Barrier Synchronization
Requirements:
• Threads wait until all threads reach the point
• Last thread signals all waiting threads
#include <pthread.h> example
#include <stdio.h>
#define NUM_THREADS 5
2.c
int main() {
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t threads[NUM_THREADS];
pthread_cond_t cond_barrier = PTHREAD_COND_INITIALIZ int thread_ids[NUM_THREADS];
ER; for (int i = 0; i < NUM_THREADS; i++) {
int count = 0; thread_ids[i] = i;
pthread_create(&threads[i], NULL, thread_func,
void* thread_func(void* arg) { &thread_ids[i]);
int id = *(int*)arg; }
printf("Thread %d reached the wait if not all id);
barrier.\n", for (int i = 0; i < NUM_THREADS; i++) {
pthread_mutex_lock(&mutex); pthread_join(threads[i], NULL);
arrive
count++; }
if (count < NUM_THREADS) { return 0;
pthread_cond_wait(&cond_barrier, &mutex); }
} else {
pthread_cond_broadcast(&cond_barrier); the last thread signals all waiting
}
pthread_mutex_unlock(&mutex); threads
printf("Thread %d passed the barrier.\n", id);
return NULL; 1
}
Exercise: Dining Philosophers Problem
Problem: Five philosophers sit around a table with five forks.
Each philosopher needs two forks to eat but only one is available
on either side. The challenge is to avoid deadlock and ensure all
philosophers eventually eat.
Solution: Use condition variables to manage fork availability.
Philosophers check if both adjacent forks are available before
picking them up. After eating, they put the forks back and signal
neighbors, allowing them to eat if possible.
Dining philosophers problem - Wikipedia
1
Exercise: Dining Philosophers Problem
#include <pthread.h> exercise. void* philosopher(void* arg) {
#include <stdio.h> int i = *(int*)arg;
#define NUM_PHILOSOPHERS 5
c while (1) {
printf("Philosopher %d is thinking.\n", i);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pickup_forks(i);
pthread_cond_t cond_vars[NUM_PHILOSOPHERS]; printf("Philosopher %d is eating.\n", i);
int state[NUM_PHILOSOPHERS]; return_forks(i);
enum { THINKING, HUNGRY, EATING }; }
return NULL;
void test(int i) { }
// TODO: Implement test function int main() {
// Hint: Check if the philosopher can start eati pthread_t philosophers[NUM_PHILOSOPHERS];
ng int ids[NUM_PHILOSOPHERS];
// by ensuring neighbors are not eating. for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
} pthread_cond_init(&cond_vars[i], NULL);
void pickup_forks(int i) { ids[i] = i;
pthread_mutex_lock(&mutex); pthread_create(&philosophers[i], NULL, philosop
// TODO: Implement pickup_forks function her, &ids[i]);
// Hint: Set state to HUNGRY and then test. }
// Wait if not able to eat. for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
pthread_mutex_unlock(&mutex); pthread_join(philosophers[i], NULL);
} }
void return_forks(int i) { for (int i = 0; i < NUM_PHILOSOPHERS; i++) {
pthread_mutex_lock(&mutex); pthread_cond_destroy(&cond_vars[i]);
// TODO: Implement return_forks function }
// Hint: Set state to THINKING and test neighbor return 0;
s. } 1
General Hints for Using Condition Variables
Always lock the mutex before using condition variables.
Use while loops for pthread_cond_wait to handle spurious
wakeups.
Ensure proper cleanup using pthread_cond_destroy.
1
Q&A