w3resource

Multithreading in C with POSIX Threads: A Complete Guide

C Programming - POSIX Threads (pthreads)

Introduction

Multithreading allows a program to perform multiple tasks concurrently, improving performance on multicore systems. In C, POSIX threads (commonly referred to as pthreads) are used for multithreading. This tutorial will explore basic thread concepts, creation, joining, and synchronization using pthread in C.

Key Topics:

POSIX threads (pthreads)

  • POSIX (Portable Operating System Interface): A standard for maintaining compatibility between operating systems, ensuring portability of code across UNIX-like systems.
  • Multithreading Library: The pthread library provides functions to create, manage, and synchronize threads in C programs.
  • Thread Creation: Use pthread_create() to create a new thread, which runs a specific function concurrently with the main program.
  • Thread Joining: Use pthread_join() to ensure the main program waits for a thread to finish before continuing execution.
  • Mutex for Synchronization: Pthreads offer mutex locks (pthread_mutex_t) to ensure that shared resources are accessed safely by multiple threads, preventing race conditions.
  • Condition Variables: Pthreads support condition variables (pthread_cond_t) to allow threads to wait for or signal certain conditions before proceeding.
  • Portability: Since pthreads adhere to the POSIX standard, programs using pthreads are portable across different UNIX-like systems, including Linux and macOS.
  • Efficiency: Multithreading can increase performance by taking advantage of multicore processors, allowing tasks to be performed simultaneously.

Syntax for POSIX threads (pthreads)

Here's a clear syntax for POSIX threads (pthreads) in C programming, along with descriptions of the primary functions and their parameters:

Thread Creation: pthread_create()

This function creates a new thread that starts execution at the function pointed to by start_routine.

Syntax:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

Parameters:

  • pthread_t *thread: Pointer to a variable that stores the thread ID of the newly created thread.
  • const pthread_attr_t *attr: Pointer to thread attributes. Use NULL for default attributes.
  • void *(*start_routine)(void *): Pointer to the function that the thread will execute. This function must return void* and take a void* argument.
  • void *arg: Argument passed to the thread’s start routine. Use NULL if no argument is needed.

Thread Termination: pthread_exit()

This function terminates the calling thread and optionally returns a value to other threads.

Syntax:

void pthread_exit(void *retval);

Parameter:

void *retval: The exit status of the thread, which can be retrieved by another thread using pthread_join().

Waiting for Thread to Complete: pthread_join()

The calling thread waits for the specified thread to terminate. If the target thread has already exited, pthread_join() returns immediately.

Syntax:

int pthread_join(pthread_t thread, void **retval);

Parameters:

  • pthread_t thread: The thread ID of the thread to wait for.
  • void **retval: Pointer to store the return value of the terminated thread (optional, can be NULL).

Thread Synchronization with Mutex: pthread_mutex_t

Mutexes are used to protect shared resources from being accessed by multiple threads at the same time. pthread_mutex_lock() locks the mutex, and pthread_mutex_unlock() releases it.

Mutex Initialization:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Mutex Locking:

int pthread_mutex_lock(pthread_mutex_t *mutex);

Parameters:

  • pthread_mutex_t *mutex: Pointer to the mutex to be locked.

Mutex Unlocking:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Parameter:

  • pthread_mutex_t *mutex: Pointer to the mutex to be unlocked.

Condition Variables: pthread_cond_t

Condition variables allow threads to wait for specific conditions to be met, typically in conjunction with a mutex.

Condition Variable Initialization:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Wait on Condition:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

Parameters:

  • pthread_cond_t *cond: Pointer to the condition variable.
  • pthread_mutex_t *mutex: Pointer to the associated mutex. The mutex is automatically unlocked while waiting.

Signal a Condition:

int pthread_cond_signal(pthread_cond_t *cond);

Parameter:

  • pthread_cond_t *cond: Pointer to the condition variable to signal.

Thread Attributes: pthread_attr_t

Attributes control thread behavior, such as whether the thread is joinable or detached.

Attribute Initialization:

pthread_attr_t attr;
pthread_attr_init(&attr);

Set Detach State (Optional):

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

Parameters:

  • pthread_attr_t *attr: Pointer to the thread attribute object.
  • int detachstate: Set the detach state, either PTHREAD_CREATE_JOINABLE (default) or PTHREAD_CREATE_DETACHED.

Example: Simple Multithreading with POSIX Threads

Following example demonstrates how to create and manage multiple threads in C using the POSIX thread (pthread) library.

Code:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// Function executed by threads
void *thread_function(void *arg) {
    int *num = (int *)arg; // Casting argument to int pointer
    printf("Thread %d is running.\n", *num);
    pthread_exit(NULL); // Thread exit with no return value
}
int main() {
    pthread_t threads[3]; // Array of thread IDs
    int args[3] = {1, 2, 3}; // Arguments to pass to each thread
    // Creating 3 threads
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, (void *)&args[i]) != 0) {
            printf("Error creating thread %d\n", i);
            return 1;
        }
    }
    // Waiting for each thread to finish
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("All threads completed.\n");
    return 0;
} 

Output:

Thread 1 is running.
Thread 2 is running.
Thread 3 is running.
All threads completed. 

Explanation:

Thread Function (thread_function):

  • This function is executed by each thread. It receives an argument (arg), which is cast to an integer pointer. The function prints a message indicating which thread is running and then terminates using pthread_exit().

Main Function:

  • An array threads[3] is created to hold the thread IDs of the three threads.
  • The args array stores values (1, 2, 3) that will be passed to each thread.
  • The pthread_create() function is used inside a loop to create three threads, each executing the thread_function and passing the corresponding element from args[].
  • If thread creation fails, an error message is printed.
  • After creating the threads, the pthread_join() function is called in a loop to wait for each thread to finish execution.
  • Once all threads have finished, the program prints "All threads completed."

Example: Creating and Joining Threads

This example demonstrates how to create and join two threads using pthread_create() and pthread_join(). Each thread prints its ID and then the main program waits for both threads to complete.

Code:

#include <stdio.h>
#include <pthread.h7gt;
// Function to be executed by each thread
void* threadFunction(void* arg) {
    int *id = (int*) arg;
    printf("Thread ID: %d\n", *id);
    return NULL;
}
int main() {
    pthread_t thread1, thread2;  // Declare thread variables
    int id1 = 1, id2 = 2;  // Arguments for threads

    // Create two threads
    pthread_create(&thread1, NULL, threadFunction, &id1);  // Thread 1
    pthread_create(&thread2, NULL, threadFunction, &id2);  // Thread 2

    // Wait for threads to complete
    pthread_join(thread1, NULL);  // Join thread 1
    pthread_join(thread2, NULL);  // Join thread 2

    printf("Threads completed.\n");
    return 0;
}

Output:

Thread ID: 1
Thread ID: 2
Threads completed.

Explanation:

  • pthread_create() creates a new thread and executes the function threadFunction.
  • The thread ID (e.g., thread1, thread2) is passed as a reference, and the thread function takes a void* argument to pass any data to the thread.
  • pthread_join() ensures the main program waits for the threads to finish.

Example: Synchronizing Threads with Mutex

This example shows thread synchronization using mutex locks to prevent race conditions while multiple threads modify a shared resource (counter).

Code:

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock;  // Declare a mutex lock
int counter = 0;       // Shared resource
// Function to increment the counter
void* incrementCounter(void* arg) {
    pthread_mutex_lock(&lock);  // Lock the critical section
    counter++;
    printf("Counter: %d\n", counter);
    pthread_mutex_unlock(&lock);  // Unlock the critical section
    return NULL;
}
int main() {
    pthread_t thread1, thread2;

    // Initialize the mutex
    pthread_mutex_init(&lock, NULL);
    // Create two threads
    pthread_create(&thread1, NULL, incrementCounter, NULL);
    pthread_create(&thread2, NULL, incrementCounter, NULL);
    // Join the threads
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    // Destroy the mutex
    pthread_mutex_destroy(&lock);
    return 0;
}

Output:

Counter: 1
Counter: 2

Explanation:

  • pthread_mutex_t is used to declare a mutex that prevents race conditions on the shared variable counter.
  • pthread_mutex_lock() locks the critical section, and pthread_mutex_unlock() unlocks it to ensure only one thread accesses the counter at a time.

Example: Passing Multiple Arguments to a Thread

This example shows how to pass multiple arguments to a thread by using a struct. The thread prints its ID and name.

Code:

#include <stdio.h>
#include <pthread.h>
// Structure to pass multiple arguments
typedef struct {
    int id;
    char name[20];
} ThreadData;

// Function to print thread details
void* printDetails(void* arg) {
    ThreadData* data = (ThreadData*) arg;
    printf("Thread ID: %d, Name: %s\n", data->id, data->name);
    return NULL;
}

int main() {
    pthread_t thread;
    ThreadData tData = {1, "WorkerThread"};  // Initialize thread data

    // Create a thread and pass the structure
    pthread_create(&thread, NULL, printDetails, &tData);

    // Join the thread
    pthread_join(thread, NULL);

    return 0;
}

Explanation:

  • A struct is used to pass multiple arguments (ID and name) to the thread.
  • In printDetails(), the ThreadData structure is accessed by casting the void* argument.

Example: Using Condition Variables

This example demonstrates the use of condition variables to synchronize threads, where one thread waits for a signal while another sends the signal.

Code:

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock;  // Declare mutex
pthread_cond_t cond;   // Declare condition variable
int ready = 0;         // Shared condition

// Function to wait for condition
void* waitForSignal(void* arg) {
    pthread_mutex_lock(&lock);
    while (!ready) {
        printf("Thread waiting for signal...\n");
        pthread_cond_wait(&cond, &lock);  // Wait for condition signal
    }
    printf("Thread received signal!\n");
    pthread_mutex_unlock(&lock);
    return NULL;
}

// Function to send signal
void* sendSignal(void* arg) {
    pthread_mutex_lock(&lock);
    ready = 1;  // Set condition to true
    pthread_cond_signal(&cond);  // Send signal to waiting thread
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    // Initialize mutex and condition variable
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);

    // Create threads
    pthread_create(&thread1, NULL, waitForSignal, NULL);
    pthread_create(&thread2, NULL, sendSignal, NULL);

    // Join threads
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Destroy mutex and condition variable
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    return 0;
}

Explanation:

  • pthread_cond_t is used for condition variables that allow threads to wait for or send signals.
  • pthread_cond_wait() makes a thread wait for a signal, and pthread_cond_signal() sends the signal when the condition is met.

Summary:

  • Thread Creation and Joining: Use pthread_create() and pthread_join() to create and manage threads.
  • Synchronization: Use mutexes to prevent race conditions when multiple threads access shared data.
  • Passing Arguments: Pass multiple arguments to threads using structures.
  • Condition Variables: Use condition variables to signal between threads and control execution flow.


Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://w3resource.com/c-programming/c-multithreading.php