Concurrency
Concurrency in C is typically achieved using threads. Threads are separate flows of execution that run independently within a process. They share the same memory space, allowing them to communicate and coordinate with each other.
Threads
The <pthread.h>
or <threads.h>
since C11 provides functions for working with threads in C.
#include <stdio.h>
#include <threads.h>
#define NUM_THREADS 2
// Function to be executed by each thread
int printHello(void *arg) {
long threadId = (long)arg;
printf("Hello from thread %ld\n", threadId);
return 0;
}
int main() {
thrd_t threads[NUM_THREADS];
// Create threads
for (long t = 0; t < NUM_THREADS; t++) {
if (thrd_create(&threads[t], printHello, (void *)t) != thrd_success) {
fprintf(stderr, "Error creating thread %ld\n", t);
return 1;
}
}
// Wait for threads to finish
for (long t = 0; t < NUM_THREADS; t++) {
int result;
if (thrd_join(threads[t], &result) != thrd_success) {
fprintf(stderr, "Error joining thread %ld\n", t);
return 2;
}
}
printf("All threads have completed.\n");
return 0;
}
Mutex
A mutex (short for "mutual exclusion") is a synchronization mechanism used in concurrent programming to ensure that only one thread at a time can access a shared resource or a critical section of code.
The purpose of using a mutex is to prevent data corruption and unexpected behavior that can occur when multiple threads attempt to modify shared data simultaneously.
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
#define NUM_THREADS 4
#define ITERATIONS 100000
int sharedCounter = 0;
mtx_t mutex; // Mutex to protect sharedCounter
// Function to be executed by each thread
int incrementCounter(void *arg) {
for (int i = 0; i < ITERATIONS; i++) {
mtx_lock(&mutex); // Lock the mutex before accessing sharedCounter
sharedCounter++;
mtx_unlock(&mutex); // Unlock the mutex after modifying sharedCounter
}
return 0;
}
int main() {
thrd_t threads[NUM_THREADS];
// Initialize the mutex
if (mtx_init(&mutex, mtx_plain) != thrd_success) {
fprintf(stderr, "Error initializing mutex.\n");
return 1;
}
// Create threads
for (long t = 0; t < NUM_THREADS; t++) {
if (thrd_create(&threads[t], incrementCounter, NULL) != thrd_success) {
fprintf(stderr, "Error creating thread %ld\n", t);
return 2;
}
}
// Wait for threads to finish
for (long t = 0; t < NUM_THREADS; t++) {
if (thrd_join(threads[t], NULL) != thrd_success) {
fprintf(stderr, "Error joining thread %ld\n", t);
return 3;
}
}
mtx_destroy(&mutex);
printf("Shared counter: %d\n", sharedCounter);
return 0;
}
- The
mtx_t
type is used to represent a mutex. - The
mtx_init
function is used to initialize the mutex. - The
mtx_lock
function is used to lock the mutex before accessing the shared data (sharedCounter). - The
mtx_unlock
function is used to unlock the mutex after modifying the shared data. - The
incrementCounter
function increments the shared counter within a loop.
Condition Variable
A condition variable is a synchronization primitive used for inter-thread communication. It provides a way for threads to block until a certain condition is met, allowing threads to coordinate their activities.
The cnd_t
type in C, part of the <threads.h>
library, represents a condition variable.
#include <stdio.h>
#include <threads.h>
cnd_t condition;
mtx_t mutex;
int sharedValue = 0;
int main_thread(void *arg) {
for (int i = 0; i < 5; ++i) {
mtx_lock(&mutex);
sharedValue = i;
printf("Main thread: Set sharedValue to %d\n", sharedValue);
cnd_broadcast(&condition);
mtx_unlock(&mutex);
thrd_yield(); // Allow other threads to run
}
return 0;
}
int worker_thread(void *arg) {
for (int i = 0; i < 5; ++i) {
mtx_lock(&mutex);
// Wait until the condition is signaled
while (sharedValue != i) {
cnd_wait(&condition, &mutex);
}
printf("Worker thread: Received sharedValue %d\n", sharedValue);
mtx_unlock(&mutex);
thrd_yield(); // Allow other threads to run
}
return 0;
}
int main() {
thrd_t mainThread, workerThread;
// Initialize mutex and condition variable
mtx_init(&mutex, mtx_plain);
cnd_init(&condition);
// Create threads
thrd_create(&mainThread, main_thread, NULL);
thrd_create(&workerThread, worker_thread, NULL);
// Wait for threads to finish
thrd_join(mainThread, NULL);
thrd_join(workerThread, NULL);
// Destroy mutex and condition variable
mtx_destroy(&mutex);
cnd_destroy(&condition);
return 0;
}
The functions related to cnd_t
are listed below:
int cnd_init(cnd_t *cond)
: Initializes a condition variable.int cnd_destroy(cnd_t *cond)
: Destroys a condition variable.int cnd_signal(cnd_t *cond)
: Signals one thread that is waiting on the condition variable. If no threads are waiting, the signal has no effect.int cnd_broadcast(cnd_t *cond)
: Signals all threads that are waiting on the condition variable. This wakes up all waiting threads.int cnd_wait(cnd_t *cond, mtx_t *mtx)
: Releases the associated mutex (mtx
) and waits on the condition variable. When the condition is signaled or broadcasted, the thread is awakened and reacquires the mutex.
Code Challenge
Write a C program that simulates a simple buffer shared between two threads. One thread (
Producer
) adds items to the buffer, and another thread (Consumer
) removes items from the buffer. Use amutex
to ensure thread safety.