# 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.