Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Multithreading in C

Introduction to Multithreading

Multithreading is a technique that allows a CPU to execute multiple threads concurrently, improving the performance and responsiveness of applications. In the context of the C programming language, multithreading is typically implemented using the POSIX Threads (pthread) library.

Setting Up

Before you start using pthreads, you need to include the pthread library in your program. This is done by including the <pthread.h> header file and linking the pthread library during compilation.

Include the pthread header:

#include <pthread.h>

Compile with pthread library:

gcc -pthread your_program.c -o your_program

Creating Threads

To create a thread, you use the pthread_create function. This function takes four arguments: a pointer to a pthread_t variable, thread attributes, the function to be executed, and the argument to be passed to the function.

Example of creating a thread:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* print_message(void* message) {
    printf("%s\n", (char*)message);
    return NULL;
}

int main() {
    pthread_t thread;
    const char* message = "Hello, Thread!";
    int result = pthread_create(&thread, NULL, print_message, (void*)message);

    if (result != 0) {
        printf("Error creating thread: %d\n", result);
        return 1;
    }

    pthread_join(thread, NULL);
    return 0;
}
                    

Joining Threads

After creating a thread, the main thread can wait for it to finish by using the pthread_join function. This function takes two arguments: the thread to wait for and a pointer to store the thread's return value.

Example of joining a thread:

#include <pthread.h>
#include <stdio.h>

void* thread_function(void* arg) {
    printf("Thread is running...\n");
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);
    pthread_join(thread, NULL);
    printf("Thread has finished.\n");
    return 0;
}
                    

Thread Synchronization

In a multithreaded environment, it's crucial to manage access to shared resources to avoid race conditions. The pthread library provides several synchronization mechanisms, including mutexes.

Using Mutexes

A mutex (mutual exclusion) is a synchronization primitive used to protect shared data from being simultaneously accessed by multiple threads. You initialize a mutex using pthread_mutex_init, lock it using pthread_mutex_lock, and unlock it using pthread_mutex_unlock.

Example of using a mutex:

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex;
int counter = 0;

void* increment_counter(void* arg) {
    pthread_mutex_lock(&mutex);
    counter++;
    printf("Counter: %d\n", counter);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    pthread_mutex_init(&mutex, NULL);

    pthread_create(&thread1, NULL, increment_counter, NULL);
    pthread_create(&thread2, NULL, increment_counter, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mutex);
    return 0;
}
                    

Condition Variables

Condition variables are used to block a thread until a particular condition is met. They are always used with a mutex to avoid race conditions. You initialize a condition variable using pthread_cond_init, wait on it using pthread_cond_wait, and signal it using pthread_cond_signal or pthread_cond_broadcast.

Example of using condition variables:

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;

void* wait_for_signal(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {
        pthread_cond_wait(&cond, &mutex);
    }
    printf("Signal received!\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* send_signal(void* arg) {
    pthread_mutex_lock(&mutex);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&thread1, NULL, wait_for_signal, NULL);
    pthread_create(&thread2, NULL, send_signal, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}
                    

Advanced Topics

In this section, we'll cover some advanced topics in multithreading, including thread pools and barriers.

Thread Pools

A thread pool is a collection of pre-initialized threads that can be reused to perform multiple tasks. This reduces the overhead of creating and destroying threads frequently.

Implementing a simple thread pool is beyond the scope of this tutorial, but you can explore libraries like libuv or Threading Building Blocks (TBB) for more advanced usage.

Barriers

A barrier is a synchronization primitive that allows multiple threads to wait until all threads have reached a certain point in the code. You initialize a barrier using pthread_barrier_init and wait on it using pthread_barrier_wait.

Example of using a barrier:

#include <pthread.h>
#include <stdio.h>

pthread_barrier_t barrier;

void* thread_task(void* arg) {
    printf("Thread %ld reached the barrier.\n", (long)arg);
    pthread_barrier_wait(&barrier);
    printf("Thread %ld passed the barrier.\n", (long)arg);
    return NULL;
}

int main() {
    pthread_t threads[3];
    pthread_barrier_init(&barrier, NULL, 3);

    for (long i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, thread_task, (void*)i);
    }

    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_barrier_destroy(&barrier);
    return 0;
}
                    

Conclusion

Multithreading is a powerful technique that can significantly improve the performance and responsiveness of applications. However, it also introduces complexity, especially with regard to synchronization and resource sharing. By mastering the basics of pthreads, you can begin to leverage the full power of multithreading in your C programs.