# Multitasking and Multithreading in C ## Introduction Multitasking and multithreading are programming techniques that enable a program to perform multiple tasks concurrently. In the C language, you can achieve multitasking through multithreading using the POSIX threads library (`pthread`). ## Multithreading in C using pthreads ### Step 1: Include the pthreads library ```c= #include <stdio.h> #include <stdlib.h> #include <pthread.h> ``` ### Step 2: Create a thread function ```c= void *thread_function(void *arg) { // Thread logic goes here printf("Hello from the thread!\n"); return NULL; } ``` - Thread Function (thread_function): This is where you define the task that the new thread will perform. It must match the signature expected by pthread_create, which is a function pointer to void* (*)(void*). ### Step 3: Create and run threads ```c= int main() { pthread_t thread_id; int result; // Create a thread result = pthread_create(&thread_id, NULL, thread_function, NULL); if (result != 0) { perror("Thread creation failed"); return 1; } // Wait for the thread to finish pthread_join(thread_id, NULL); return 0; } ``` - pthread_create() parameters - pthread_t *thread: A pointer to pthread_t, which is an identifier for the new thread. After pthread_create successfully completes, this will hold the ID of the created thread. - const pthread_attr_t *attr: A pointer to pthread_attr_t which specifies attributes for the thread. If this parameter is NULL, the thread is created with default attributes. - void *(*start_routine) (void *): A pointer to the function to be executed by the thread. This function should take a single void * argument and return a void * value. - void *arg: A pointer to the argument that will be passed to the start_routine function. - Returns 0 on successful thread creation. ## Coordination between Threads ### Synchronization with Mutex ```c= #include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int shared_data = 0; void *thread_function(void *arg) { // Lock the mutex before accessing shared data pthread_mutex_lock(&mutex); // Access and modify shared data shared_data++; printf("Thread: Incremented shared_data to %d\n", shared_data); // Unlock the mutex when done pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t thread_id; int result; // Create a thread result = pthread_create(&thread_id, NULL, thread_function, NULL); if (result != 0) { perror("Thread creation failed"); return 1; } // Wait for the thread to finish pthread_join(thread_id, NULL); return 0; } ``` - pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; initializes the mutex to a default state. This is a static way of initializing a mutex. It's set up before the threads are created and ensures that the mutex is available for use as soon as the program starts. - Locking the Mutex: pthread_mutex_lock(&mutex); This function is called before accessing the shared data. This ensures that only one thread can modify shared_data at a time. If the mutex is already locked by another thread, the calling thread will block until the mutex becomes available. ### Thread Communication with Condition Variables ```c= #include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condition = PTHREAD_COND_INITIALIZER; int shared_data = 0; void *producer_function(void *arg) { // Lock the mutex before modifying shared data pthread_mutex_lock(&mutex); // Produce data shared_data++; printf("Producer: Produced data, shared_data = %d\n", shared_data); // Signal the consumer that new data is available pthread_cond_signal(&condition); // Unlock the mutex pthread_mutex_unlock(&mutex); return NULL; } void *consumer_function(void *arg) { // Lock the mutex before checking shared data pthread_mutex_lock(&mutex); // Wait for new data to be produced while (shared_data == 0) { pthread_cond_wait(&condition, &mutex); } // Consume data shared_data--; printf("Consumer: Consumed data, shared_data = %d\n", shared_data); // Unlock the mutex pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t producer_thread, consumer_thread; int result; // Create producer thread result = pthread_create(&producer_thread, NULL, producer_function, NULL); if (result != 0) { perror("Producer thread creation failed"); return 1; } // Create consumer thread result = pthread_create(&consumer_thread, NULL, consumer_function, NULL); if (result != 0) { perror("Consumer thread creation failed"); return 1; } // Wait for threads to finish pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); return 0; } ``` - pthread_cond_wait pthread_cond_t *cond: A pointer to the condition variable. pthread_mutex_t *mutex: A pointer to the mutex associated with the condition variable. pthread_cond_wait performs several actions atomically: 1. Release the Mutex: It releases the mutex pointed to by *mutex. This allows other threads to acquire the mutex and make changes to the shared state. 2. Wait for the Condition: The calling thread is blocked and waits for the condition variable *cond to be signaled. This wait is typically in response to some shared state reaching a particular condition. 4. Re-acquire the Mutex: When the condition variable is signaled (using pthread_cond_signal or pthread_cond_broadcast), the waiting thread wakes up and automatically re-acquires the mutex. It then continues execution after the pthread_cond_wait call.