How to Implement Multithreading in C Language

Threads vs. Processes:

  • A process is an independent execution unit with its own memory space, system resources, and a unique process ID (PID).
  • A thread is a smaller unit of execution within a process. Multiple threads within the same process share the same memory space and system resources but have their own thread ID (TID).

Shared Process ID:

  • All threads created within a single process share the same process ID because they belong to the same process. Threads don’t create new processes; they work within the boundaries of the parent process.

Table of Content

Table of Contents

Threads, Multi-Threading and Locks

Multithreading: Efficient Program Execution

  • Allows multiple threads (small units of a process) to run within a single program
  • Enables simultaneous task execution, improving efficiency
  • Useful for handling multiple users or requests without running multiple copies of the program
  • Threads share memory and data but maintain individual identities

Locks in Multithreading

  • Purpose: Control access to shared resources (memory or data)
  • Prevent errors caused by multiple threads accessing the same data simultaneously
  • Basic concept:
    1. Lock before accessing shared data
    2. Unlock after accessing the data
    3. Other threads wait if a lock is already held

Basic Example of Multi-Threading

				
					#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

// Global variable that is shared among threads
int g = 0;

// Thread function that will be executed by all threads
void* myThreadFun(void* vargp)
{
    // Get the thread's process ID
    int myid = getpid();

    // Static variable shared among all threads
    static int s = 0;

    // Increment the static and global variables
    ++s;
    ++g;

    // Print the thread's ID and the current values of static and global variables
    printf("Process ID: %d, Static: %d, Global: %d\n", myid, ++s, ++g);
}

int main()
{
    int i;
    pthread_t tid;

    // Create three threads that execute myThreadFun
    for (i = 0; i < 3; i++)
        pthread_create(&tid, NULL, myThreadFun, NULL);

    // Wait for all threads to finish
    pthread_exit(NULL);
    return 0;
}

				
			

How it Works:

  1. Global Variable (g):

    • g is a global variable that is shared by all threads. When any thread increments it, the updated value is visible to other threads.
  2. Static Variable (s):

    • s is a static variable inside the myThreadFun function. This means it is shared among all threads, but its scope is limited to the function. Since it’s static, its value persists between function calls. Each thread increments s, and the changes are reflected in every subsequent thread that accesses it.
  3. Thread Creation:

    • The program creates 3 threads using the pthread_create() function. Each thread runs the myThreadFun() function.
    • The pthread_exit() in main() ensures that the program doesn’t exit before all threads have finished execution.
  4. Thread Execution:

    • Each thread prints the process ID (obtained using getpid()), the incremented values of the static variable s, and the global variable g. Even though the function is executed separately by each thread, the static and global variables are shared.

Threaded Counter with Mutex Lock

Here’s an example where a mutex is used to protect shared data:

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

int counter = 0;
pthread_mutex_t lock;

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

int main() {
    pthread_t t1, t2;

    pthread_mutex_init(&lock, NULL);

    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&lock);
    return 0;
}

				
			

Explanation:

  • counter: A shared variable that both threads will modify. Since multiple threads are accessing and modifying it, we need to protect it using a mutex.
  • pthread_mutex_t lock: A mutex (short for mutual exclusion) used to prevent concurrent access to the counter variable. This ensures that only one thread can modify counter at a time, avoiding race conditions.
  • void* thread_func(void* arg): This is the function that each thread will execute. In this case, it takes one argument (arg), which is not used here (passed as NULL when calling the function).
  • pthread_mutex_lock(&lock): The thread acquires the mutex lock before modifying the shared counter. This ensures that only one thread can increment the counter at a time.
  • counter++: The shared counter variable is incremented.
  • printf("Counter: %d\n", counter): The current value of counter is printed.
  • pthread_mutex_unlock(&lock): After modifying the counter, the thread releases the mutex lock, allowing other threads to acquire the lock and access the shared variable.
  • return NULL: Since void* is the return type, the function returns NULL (no data is returned by this function).
  • pthread_t t1, t2: These are thread identifiers for two threads t1 and t2.
  • pthread_mutex_init(&lock, NULL): Initializes the mutex lock. The second parameter is optional and can be set to NULL for default mutex behavior.
  • pthread_create(&t1, NULL, thread_func, NULL): This function creates a new thread t1, which will start executing the thread_func function.
  • pthread_create(&t2, NULL, thread_func, NULL): Similarly, this creates a second thread t2 to execute thread_func.
  • pthread_join(t1, NULL): The pthread_join function waits for thread t1 to finish executing. This ensures that the main thread does not exit before t1 has completed its task.
  • pthread_mutex_destroy(&lock): After all threads are done, the mutex lock is destroyed to release any resources allocated for the mutex.

Output:

				
					Counter: 1
Counter: 2
				
			

Practice Question:

Q) Summing an Array Using Multiple Threads

Write a program that sums the elements of an array using multiple threads. Divide the array into equal parts, and assign each part to a separate thread. Each thread will sum the elements of its portion, and the main thread will collect and sum the results from each thread.

  • Use a mutex lock to ensure that only one thread at a time updates the global sum.
  • Example: If the array is {1, 2, 3, 4, 5, 6}, and you divide it between two threads, one thread will sum {1, 2, 3} and the other will sum {4, 5, 6}. Both results should be combined in the main thread to get the final sum.