# What's ```pthread_mutex_t``` ?
```pthread_mutex_t``` is a arbitrary structure in c that represent locks that can perform lock and unlock for threads
`pthread_mutex_t` is a structure defined in the **POSIX Threads (Pthreads)** library, and it represents a **mutex (mutual exclusion)** object. This mutex allows threads to **lock** and **unlock** access to shared resources, ensuring that only one thread can operate on the resource at any given time. It's a fundamental tool for synchronizing threads and preventing race conditions in multithreaded programs.
---
### **Key Features of `pthread_mutex_t`:**
1. **Represents a Lock:**
- The `pthread_mutex_t` object acts as a lock that threads can acquire or release.
- When a thread locks the mutex, other threads attempting to lock it will block (wait) until the mutex is unlocked.
2. **Predefined in C:**
- It is not an arbitrary structure but part of the POSIX standard.
- The exact implementation details are abstracted from the programmer and handled by the system.
3. **Thread-Safe Operations:**
- It provides mechanisms to ensure only one thread accesses a critical section at a time.
---
### **Basic Operations:**
The mutex provides these key operations:
1. **Initialize the Mutex:**
- Use `pthread_mutex_init` to set up a mutex.
```c
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
```
- You can also use `PTHREAD_MUTEX_INITIALIZER` for static initialization:
```c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
```
2. **Lock the Mutex:**
- A thread locks the mutex before entering a critical section:
```c
pthread_mutex_lock(&mutex);
```
- If another thread has already locked the mutex, this call blocks until the mutex becomes available.
3. **Unlock the Mutex:**
- After completing its operation on the shared resource, the thread unlocks the mutex:
```c
pthread_mutex_unlock(&mutex);
```
4. **Destroy the Mutex:**
- When the mutex is no longer needed, free its resources:
```c
pthread_mutex_destroy(&mutex);
```
---
### **Example: Protecting a Shared Resource**
Here’s a simple example using `pthread_mutex_t` to protect a shared counter:
```c
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Static mutex initialization
int counter = 0;
void *increment_counter(void *arg)
{
for (int i = 0; i < 1000; i++) {
pthread_mutex_lock(&mutex); // Lock the mutex
counter++; // Critical section
pthread_mutex_unlock(&mutex); // Unlock the mutex
}
return NULL;
}
int main()
{
pthread_t t1, t2;
pthread_create(&t1, NULL, increment_counter, NULL);
pthread_create(&t2, NULL, increment_counter, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final counter value: %d\n", counter);
pthread_mutex_destroy(&mutex); // Clean up the mutex
return 0;
}
```
---
### **How Does It Work?**
- `pthread_mutex_t` ensures that only one thread at a time can increment the shared `counter`.
- Without the mutex, both threads could read and write `counter` simultaneously, leading to incorrect results (a race condition).
---
### **Summary**
`pthread_mutex_t` is a predefined structure in C that acts as a synchronization primitive for managing **locks** in multithreaded environments. It is essential for ensuring thread safety when accessing shared resources.
---
# Important pthread_ related Functions
## `pthread_create`
`pthread_create` is a function used to create a new thread in a program that uses POSIX threads (pthreads). A thread is a lightweight process that can run concurrently with other threads in the same program. The `pthread_create` function is used to spawn a new thread and define what that thread will do when it starts.
#### Function Signature
```c
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
```
#### Parameters:
1. **`pthread_t *thread`**:
- This is the pointer to a `pthread_t` variable that will hold the ID of the newly created thread.
- The thread ID is unique and is used to identify the thread for future operations, such as joining or canceling.
2. **`const pthread_attr_t *attr`**:
- This is a pointer to a `pthread_attr_t` structure that defines various thread attributes (like stack size or scheduling policy).
- If this is `NULL`, the default thread attributes are used (standard settings).
3. **`void *(*start_routine) (void *)`**:
- This is a pointer to the function that the new thread will execute when it starts. The function must take a `void *` argument (this is often used to pass a pointer to data) and return a `void *`.
- The function signature for the start routine must be `void *start_routine(void *arg)`.
4. **`void *arg`**:
- This is a pointer to the data that you want to pass to the thread's start routine. The argument can be any data type cast to `void *`, but you often pass a pointer to some struct or variable that the thread needs.
#### Return Value:
- **`0`**: The function succeeds in creating a thread.
- **Non-zero value**: An error code if the thread could not be created.
#### Example:
Here’s a simple example where we create a thread that prints a message. The thread executes a function called `print_message` when it starts.
```c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *print_message(void *arg) {
printf("Hello from the new thread! Argument: %s\n", (char *)arg);
return NULL;
}
int main() {
pthread_t thread; // Declare a thread variable
char *message = "This is a thread argument!"; // Argument to pass to the thread
// Create a new thread that executes the print_message function
if (pthread_create(&thread, NULL, print_message, (void *)message)) {
printf("Error creating thread\n");
return 1;
}
// Wait for the thread to finish before continuing
if (pthread_join(thread, NULL)) {
printf("Error joining thread\n");
return 1;
}
printf("Main thread finished\n");
return 0;
}
```
### Explanation of the Example:
- **`pthread_create(&thread, NULL, print_message, (void *)message)`**:
- This creates a new thread and passes the string `message` as an argument to the `print_message` function.
- The new thread will execute `print_message` with `message` as the argument, which will print `"Hello from the new thread! Argument: This is a thread argument!"`.
- **`pthread_join(thread, NULL)`**:
- This is used to wait for the new thread to finish before proceeding. The main thread blocks until the `thread` finishes executing.
- If the thread had not finished when the `pthread_join` is called, the main thread would wait for it to complete.
- **Output**:
```
Hello from the new thread! Argument: This is a thread argument!
Main thread finished
```
### Key Points:
- Threads are executed concurrently, meaning that the main thread can continue its execution while the new thread runs in parallel.
- The start routine (in this case, `print_message`) is where the thread performs its task. You can pass any data to the thread's routine by using the `arg` parameter.
- `pthread_create` doesn't block the main thread. To ensure the main thread waits for the new thread to finish, we use `pthread_join`.
### Summary:
`pthread_create` is essential for creating new threads in a multi-threaded program. It allows different parts of your program to run concurrently, improving efficiency. After creating a thread, you can use `pthread_join` to synchronize with it and wait for its completion.
---
## `pthread_join()`
`pthread_join` is a function that allows a thread to wait for another thread to finish its execution. It is used to synchronize threads by blocking the calling thread until the specified thread has finished executing. It ensures that the resources used by the thread (like memory) are cleaned up only after the thread has finished.
#### Function Signature:
```c
int pthread_join(pthread_t thread, void **retval);
```
#### Parameters:
1. **`pthread_t thread`**:
- The ID of the thread that the calling thread wants to wait for. It is a `pthread_t` type variable that identifies a specific thread created using `pthread_create`.
2. **`void **retval`**:
- A pointer to a pointer where the exit status of the thread will be stored. This is where the return value of the thread's start routine will be saved.
- If you don't need the return value, you can pass `NULL`.
#### Return Value:
- **`0`**: The function succeeds and the calling thread successfully joins with the specified thread.
- **Non-zero value**: An error occurred (e.g., if the specified thread does not exist or the calling thread has already joined with it).
#### Key Points:
- `pthread_join` is necessary to ensure proper cleanup of resources used by a thread.
- Without `pthread_join`, the main thread may terminate while other threads are still running, causing undefined behavior and resource leakage.
- It is used when you need to wait for a thread to complete before proceeding further in the program.
### Example 1: Basic Usage of `pthread_join`
Here’s a simple example where a thread is created to print a message, and the main thread waits for the new thread to finish using `pthread_join`.
```c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *print_message(void *arg) {
printf("Hello from the new thread!\n");
return NULL;
}
int main() {
pthread_t thread; // Declare a thread variable
// Create a new thread that executes the print_message function
if (pthread_create(&thread, NULL, print_message, NULL)) {
printf("Error creating thread\n");
return 1;
}
// Wait for the thread to finish before continuing
if (pthread_join(thread, NULL)) {
printf("Error joining thread\n");
return 1;
}
printf("Main thread finished\n");
return 0;
}
```
#### Explanation:
- The main thread creates a new thread using `pthread_create`.
- The main thread then calls `pthread_join(thread, NULL)` to wait for the new thread (`thread`) to finish before proceeding with the rest of the program.
- The output will be:
```
Hello from the new thread!
Main thread finished
```
### Example 2: `pthread_join` with Return Values
In this example, the thread returns a value, and the main thread retrieves it using `pthread_join`.
```c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *calculate_square(void *arg) {
int *num = (int *)arg;
int *result = malloc(sizeof(int)); // Allocate memory for the result
*result = (*num) * (*num);
return result;
}
int main() {
pthread_t thread; // Declare a thread variable
int num = 5;
int *square;
// Create a new thread that calculates the square of 'num'
if (pthread_create(&thread, NULL, calculate_square, &num)) {
printf("Error creating thread\n");
return 1;
}
// Wait for the thread to finish and get the return value
if (pthread_join(thread, (void **)&square)) {
printf("Error joining thread\n");
return 1;
}
printf("The square of %d is %d\n", num, *square);
free(square); // Don't forget to free the allocated memory
return 0;
}
```
#### Explanation:
- The thread calculates the square of a number and returns the result.
- The `pthread_join` function is used to obtain the return value (`square`) from the thread. The return value is a pointer to an integer, which is cast to `void **` for passing to `pthread_join`.
- The output will be:
```
The square of 5 is 25
```
#### Key Notes:
- **Return values in threads**: The `return` statement in a thread's start routine can be used to send back a result. The main thread retrieves this result by calling `pthread_join` and providing a location to store the return value.
- **Memory management**: If the thread dynamically allocates memory (as in the second example), the main thread is responsible for freeing it after it is done using the returned value to avoid memory leaks.
### Example 3: Using `pthread_join` in a Loop (Multiple Threads)
Here’s an example of creating multiple threads and using `pthread_join` to wait for all of them to finish.
```c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *print_thread_id(void *arg) {
printf("Thread ID: %ld\n", pthread_self());
return NULL;
}
int main() {
pthread_t threads[3]; // Declare an array of threads
int i;
// Create 3 threads
for (i = 0; i < 3; i++) {
if (pthread_create(&threads[i], NULL, print_thread_id, NULL)) {
printf("Error creating thread %d\n", i);
return 1;
}
}
// Wait for all threads to finish
for (i = 0; i < 3; i++) {
if (pthread_join(threads[i], NULL)) {
printf("Error joining thread %d\n", i);
return 1;
}
}
printf("All threads finished\n");
return 0;
}
```
#### Explanation:
- We create 3 threads, each of which prints its thread ID using `pthread_self()`.
- After creating all the threads, the main thread uses `pthread_join` in a loop to wait for each thread to finish before proceeding.
- The output will be:
```
Thread ID: 139747349055744
Thread ID: 139747340663040
Thread ID: 139747332270336
All threads finished
```
### Summary:
- `pthread_join` is used to wait for a thread to finish. It ensures that the calling thread (usually the main thread) waits for the specified thread to complete before continuing.
- It is useful for synchronization and cleaning up resources used by the thread.
- You can also use `pthread_join` to retrieve the return value of the thread by passing a pointer to where the result should be stored.
- Always remember to manage memory properly if the thread allocates resources that the main thread needs to free later.
---
## Usage Example
Here is a simple program that demonstrates how to use `pthread_create()` and `pthread_join()` in C. In this example, we create multiple threads that print their thread IDs, and the main thread waits for each of them to finish using `pthread_join()`.
### Simple Program Example
```c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
// Function to be executed by each thread
//pthread_self() : returns the unique identifier (ID) of the calling thread.
//pthread_t thread_id = pthread_self();
void *print_thread_id(void *arg)
{
pthread_t thread_id = pthread_self();
printf("Thread ID: %ld\n", thread_id); // Print the thread ID
return NULL;
}
int main()
{
pthread_t threads[3]; // Array to hold thread IDs
int i;
// Create 3 threads
for (i = 0; i < 3; i++)
{
if (pthread_create(&threads[i], NULL, print_thread_id, NULL)) {
printf("Error creating thread %d\n", i);
return 1;
}
}
// Wait for all threads to finish
for (i = 0; i < 3; i++)
{
if (pthread_join(threads[i], NULL))
{
printf("Error joining thread %d\n", i);
return 1;
}
}
printf("All threads finished\n");
return 0;
}
```
### Explanation:
1. **`print_thread_id` Function**:
- Each thread executes this function. It retrieves its thread ID using `pthread_self()` and prints it.
2. **Creating Threads**:
- In the `main` function, we create 3 threads using a loop. For each iteration, `pthread_create` is called to create a new thread that will run the `print_thread_id` function.
3. **Joining Threads**:
- After creating the threads, the main thread waits for each thread to finish using `pthread_join`. This ensures that the main thread does not exit until all created threads have completed their execution.
4. **Output**:
- Each thread prints its unique thread ID.
- After all threads have finished, the main thread prints "All threads finished".
### Example Output:
```
Thread ID: 139937358019328
Thread ID: 139937349626624
Thread ID: 139937341233920
All threads finished
```
In this output, the thread IDs may differ each time you run the program. The key point is that `pthread_create` launches the threads, and `pthread_join` makes sure the main thread waits for all of them to finish before printing "All threads finished".
---
## Usage for pthread_create/join/detatch
### Usage Logic Brief
For everytime a thread is created with ```pthread_create```, it should always be handled when the thread is terminated;
If not using ```pthread_join```, should still use ```pthread_detach```to handle the terminated threads.
The logic is like malloc/calloc and free.
Yes, you are correct in understanding that every time you create a thread using `pthread_create()`, you need to properly handle its termination, just as you would manage memory allocation and deallocation with `malloc/calloc` and `free`.
---
### Threads and Resource Management
- When a thread is created, it consumes system resources, including memory for its stack, and has an associated process ID (PID). Just like memory allocated with `malloc` or `calloc`, these resources need to be cleaned up when the thread finishes. If you don't properly handle its termination, you risk resource leaks or "zombie" threads, just like you'd risk memory leaks if you don’t `free` allocated memory.
---
### `pthread_join()`
- **Purpose**: Waits for the thread to finish and ensures that resources associated with the thread are cleaned up once it's done.
- **Use Case**: When you need to **wait** for the thread to finish, and potentially collect any return values or do some cleanup after it finishes.
- **Similar to `free`**: Just as you call `free` to deallocate memory, you call `pthread_join()` to properly deallocate thread resources (i.e., the thread terminates, and its resources are freed).
---
### `pthread_detach()`
- **Purpose**: Detaches the thread so that it will clean up its own resources once it finishes. No need to call `pthread_join()` after detaching.
- **Use Case**: When you **don’t need to wait** for the thread to finish or collect its result but still want the system to clean up after it once it terminates.
- **Similar to garbage collection**: You don’t need to manually clean up the thread; it happens automatically when the thread finishes, similar to how garbage collection might automatically clean up unused memory in some languages. However, it’s not the same as `free` because `pthread_detach()` doesn't let you directly control when the resources are cleaned up.
---
### The Analogy with `malloc/calloc` and `free`
The logic of handling threads is indeed similar to managing dynamically allocated memory. Consider these parallels:
- **`malloc/calloc`** → **`pthread_create()`**:
- Just like memory is allocated using `malloc` or `calloc`, a thread is created with `pthread_create`.
- **`free`** → **`pthread_join()`**:
- Just as you need to call `free` to deallocate memory, you need to call `pthread_join()` to clean up the resources associated with a thread.
- **`free` vs. `pthread_detach()`**:
- Unlike `free`, which deallocates memory, `pthread_detach()` allows the system to automatically clean up the thread's resources when it finishes. You don't need to manually call `pthread_detach()`, but it's essential if you don’t want to leave zombie threads.
---
### Example: Thread Handling with `pthread_create()`, `pthread_join()`, and `pthread_detach()`
#### Example with `pthread_join()`:
```c
#include <stdio.h>
#include <pthread.h>
void* thread_function(void *arg) {
printf("Thread is running...\n");
return NULL;
}
int main() {
pthread_t thread;
// Create a thread
pthread_create(&thread, NULL, thread_function, NULL);
// Wait for the thread to finish and clean up resources
pthread_join(thread, NULL); // Like calling free() for memory
printf("Thread has finished\n");
return 0;
}
```
#### Example with `pthread_detach()`:
```c
#include <stdio.h>
#include <pthread.h>
void* thread_function(void *arg) {
printf("Thread is running...\n");
return NULL;
}
int main() {
pthread_t thread;
// Create a thread
pthread_create(&thread, NULL, thread_function, NULL);
// Detach the thread so it cleans up automatically when finished
pthread_detach(thread); // Thread is cleaned up automatically when it finishes
// Main can continue without waiting for the thread
printf("Main thread continues...\n");
return 0;
}
```
---
### Key Points:
- **When to use `pthread_join()`**:
- If you need to wait for the thread to finish, use `pthread_join()` to ensure resources are cleaned up.
- **When to use `pthread_detach()`**:
- If you don’t need to wait for the thread or collect its return value, use `pthread_detach()`, so the thread cleans up automatically when it terminates.
- **What happens without `pthread_join()` or `pthread_detach()`**:
- If you don’t handle thread termination using either `pthread_join()` or `pthread_detach()`, you may end up with **zombie threads**, where the thread finishes but its resources are not cleaned up, which can lead to memory leaks.
So, just as memory allocated with `malloc/calloc` must be freed to avoid memory leaks, threads created with `pthread_create()` need to be properly cleaned up, either by using `pthread_join()` (if you wait for the thread) or `pthread_detach()` (if you don’t).
---
# Bash Commands to Check Thread Usage
To check if threads are properly handled in a program, you can use the following Bash commands and tools:
### 1. **`htop` or `top`**
- **Purpose**: Monitor running processes and threads in real time.
- **Usage**:
```bash
htop
```
or
```bash
top -H -p <PID>
```
- **Explanation**:
- In `htop`, press `F2` (Setup) and enable "Display threads in a process."
- In `top`, the `-H` flag shows threads, and `-p` focuses on a specific process ID.
- **What to Check**:
- Look for the number of threads (`TID`) created by your program.
- Ensure there aren’t excessive threads lingering after they should have terminated.
---
### 2. **`ps`**
- **Purpose**: Display thread information for a specific process.
- **Usage**:
```bash
ps -eLf | grep <program_name>
```
- **Explanation**:
- `-eL`: Show all processes with their threads (lightweight processes).
- `f`: Full-format listing.
- `grep`: Filter output for your program.
- **What to Check**:
- Count the number of threads (`LWP`) associated with your program.
- Ensure terminated threads are no longer listed.
---
### 3. **`pmap`**
- **Purpose**: Check memory usage, which can indicate thread leaks.
- **Usage**:
```bash
pmap <PID>
```
- **Explanation**:
- `pmap` shows memory maps for a given process ID.
- Look for excessive memory usage, which may indicate resource leaks due to unhandled threads.
---
### 4. **`valgrind` (Optional for Debugging)**
- **Purpose**: Detect memory and thread leaks during program execution.
- **Usage**:
```bash
valgrind --tool=helgrind ./your_program
```
- **Explanation**:
- `helgrind` is a thread debugger that detects thread-related errors, including resource leaks.
- Look for reports of unhandled threads or memory leaks.
---
### 5. **`strace`**
- **Purpose**: Trace system calls, including thread creation and termination.
- **Usage**:
```bash
strace -f ./your_program
```
- **Explanation**:
- `-f` tracks child threads created by your program.
- Look for system calls like `clone()` (used for thread creation) and ensure there’s a corresponding cleanup.
---
### Key Indicators of Proper Thread Handling:
- The number of threads remains stable over time.
- No excessive or lingering threads after the program finishes execution.
- Memory usage doesn’t grow uncontrollably.
- Tools like `valgrind` or `helgrind` report no thread or memory leaks.
By combining these tools, you can confidently check whether threads are properly managed in your program.