» Quick Introduction to C » 2. Advanced » 2.2 Concurrency

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 a mutex to ensure thread safety.

Loading...
> code result goes here
Prev
Next