Thread Fundamentals in Linux
Threads represent lightweight execution units within a process. In the Linux environment, threads are fundamentally implemented as processes. While a process possesses its own independent address space and Process Control Block (PCB), operating like a standalone residence, threads have their own PCBs but share the process's address space, similar to cohabiting tenants.
Linux Operating System Perspective:
- Threads are the smallest execution units
- Processes are the smallest resource allocation units, which can be viewed as processes containing only a single thread
Thread Characteristics:
Unix-like systems initially lacked the concept of "threads" until their introduction in the 1980s. These systems implemented thread concepts using existing process mechanisms. Therefore, threads and processes maintain a close relationship in such systems.
Threads are considered lightweight processes (LWPs) and also have PCBs. The underlying function for creating threads is the same as for processes - both use clone. Whether calling fork to create a process or pthread_create to create a thread, the underlying implementation envokes the same kernel function clone. If the address space is duplicated, it creates a "process"; if the address space is shared, it creates a "thread".
Shared Resources
- Address Space: All threads share the same virtual address space, enabling direct access to global variables, static variables, and dynamically allocated heap memory within the same process.
- File Descriptors: Open files, network connections, and other file descriptors are process-level resources shared among all threads.
- Signal Handlers: Process-level signal handlers and signal handling functions are shared across all threads.
- Process Attributes: Process ID (PID), Process Group ID (PGID), Session ID (SID), and other process-level identifiers are shared.
Non-Shared Resources
- Thread-Specific Resources: Each thread has an independent Thread ID (TID), Thread Local Storage (TLS), and unique register context (including stack pointer, program counter).
- Stack Space: Each thread maintains its own stack for storing local variables and temporary data from function calls.
- Thread Control Block (TCB): Each thread has its own TCB storing thread state, scheduling information, and priority.
- Thread Local Storage (TLS): Threads can have private TLS for storing thread-specific data invisible to other threads.
Advantages and Disadvantages
Advantages:
- Fast Response: Threads share the same address space, enabling rapid inter-thread communication and data transfer.
- Low Resource Overhead: Thread creation and destruction requires minimal overhead due to resource sharing.
- High Concurrency: Threads effectively utilize multi-core processors to improve program concurrency and performance.
- Simplified Programming: Inter-thread data sharing and communication is more straightforward than Inter-Process Communication (IPC).
Disadvantages:
- Shared Data Risks: Shared data requires synchronization mechanisms to prevent race conditions and data inconsistency.
- Debugging Complexity: Multiple threads sharing the same address space make error tracking and debugging more challenging.
- Stability Issues: A thread crash can affect the entire process's stability due to shared resources.
Common Thread Library Functions
Thread Creation and Termination
pthread_create()
The pthread_create function initializes a new thread, specifying the starting execution address as a function. The new thread begins executing at this function.
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Parameters:
thread: Pointer to a pthread_t variable storing the new thread's identifierattr: Thread attributes (typically NULL for default attributes)start_routine: Pointer to the thread function to executearg: Argument passed to the thread function (void *)
Return Values:
- 0 on successful thread creation
- Positive enteger on error (check errno for specific error codes)
The thread function must accept a void * parameter and return a void * pointer. The created thread is executable but may not run immediately - execution depends on the scheduler. Use pthread_join to wait for thread completion and retrieve return values.
Example Implementation:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
// Thread execution function that processes input data
void *worker_task(void *input) {
char *data = (char *)input;
int length = strlen(data);
printf("Worker processing: '%s' (length: %d)\n", data, length);
// Simulate processing
for (int i = 0; i < 3; i++) {
printf("Processing step %d...\n", i + 1);
sleep(1);
}
printf("Worker completed task\n");
pthread_exit((void *)length);
}
int main() {
pthread_t worker_tid;
char *payload = "Thread sample data";
void *result;
// Initialize thread with payload data
int status = pthread_create(&worker_tid, NULL, worker_task, (void *)payload);
if (status != 0) {
fprintf(stderr, "Thread creation failed with code: %d\n", status);
return EXIT_FAILURE;
}
// Wait for thread completion
pthread_join(worker_tid, &result);
printf("Main thread received return value: %ld\n", (long)result);
return EXIT_SUCCESS;
}