Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Managing Threads with POSIX Threads (pthreads)

Tech May 17 2

Thread management involves creation, termination, and synchronization. In Linux, the operating system kernel views threads as lightweight processes sharing the same address space. Since the standard system libraries do not provide a dedicated threading interface, developers rely on the POSIX Threads (pthread) library for multi-threaded programming.

Creating Threads with pthread_create

This function is used to spawn a new thread.

  • pthread_t *thread: A pointer to store the unique thread identifier.
  • const pthread_attr_t *attr: Attributes for the new thread, often set to NULL for defaults.
  • void *(*start_routine)(void *): The function pointer for the thread's entry point.
  • void *arg: The single argument passed to the thread function.
  • Returns: 0 on success, an errer code on failure.

Thread Termination Methods

A thread can end its execution in several ways:

  1. Returning from its main function.
  2. Calling pthread_exit(void *retval) explicitly.
  3. Being canceled by another thread via pthread_cancel(pthread_t thread).

Joining Threads with pthread_join

This function blocks the calling thread until the specified target thread terminates.

  • pthread_t thread: The identifier of the thread to wait for.
  • void **retval: An output parameter that receives the pointer to the thread's return value. Its type is void ** because the thread function returns void *.

Detaching Threads with pthread_detach

If a thread's return value is not needed, joining it creates unnecessary overhead. However, a thread must be reclaimed to prevent resource leaks. Detaching a thread informs the system to automatically release its resources upon completion. A thread can be detached by another thread using its ID, or it can detach itself using pthread_detach(pthread_self()).

Example: Multi-threaded Sum Calculation

#include <iostream>
#include <pthread.h>
#include <unistd.h>

struct WorkUnit {
    int lower_bound;
    int upper_bound;
    const char* task_label;
    WorkUnit(int low, int high, const char* label)
        : lower_bound(low), upper_bound(high), task_label(label) {}
};

struct ComputationResult {
    int total_sum;
    int status_code;
    ComputationResult(int sum, int code) : total_sum(sum), status_code(code) {}
};

void* compute_range(void* input) {
    WorkUnit* unit = static_cast<WorkUnit*>(input);
    ComputationResult* outcome = new ComputationResult(0, 0);
    for (int num = unit->lower_bound; num <= unit->upper_bound; ++num) {
        usleep(1000); // Simulate work
        outcome->total_sum += num;
        std::cout << unit->task_label << " (PID: " << getpid() << ") adding: " << num << std::endl;
    }
    delete unit; // Free memory allocated by main thread
    return outcome;
}

int main() {
    pthread_t worker_id;
    WorkUnit* unit = new WorkUnit(0, 50, "WorkerThread");
    std::cout << "Main thread (PID: " << getpid() << ") starting new thread" << std::endl;
    pthread_create(&worker_id, NULL, compute_range, unit);
    void* raw_output;
    pthread_join(worker_id, &raw_output);
    ComputationResult* final_result = static_cast<ComputationResult*>(raw_output);
    std::cout << unit->task_label << " final sum = " << final_result->total_sum << std::endl;
    delete final_result;
    return 0;
}

Compile with -lpthread. All threads in a process share the same PID but have unique Lightweight Process IDs (LWP).

Thread IDs and Internal Management

Linux does not implement a native "thread" concept in its kernel; it uses the clone() system call to create execution units sharing resources. The pthread library manages these units, creating Thread Control Blocks (TCBs) and other metadata. This library is loaded into a process's shared memory region. The thread ID (pthread_t) is typically an address within this library's data structures.

Thread Stacks

Each thread has its own independent call stack. Variables allocated on a thread's stack are local to that thread.

Stack Independence Example:

#include <iostream>
#include <pthread.h>
#include <vector>
const int THREAD_COUNT = 4;
void* thread_task(void*) {
    int local_var = 42;
    return reinterpret_cast<void*>(&local_var);
}
int main() {
    std::vector<pthread_t> threads;
    for (int i = 0; i < THREAD_COUNT; ++i) {
        pthread_t id;
        pthread_create(&id, NULL, thread_task, NULL);
        threads.push_back(id);
    }
    for (auto& id : threads) {
        void* addr;
        pthread_join(id, &addr);
        std::cout << "Stack address: " << reinterpret_cast<long>(addr) << std::endl;
    }
    return 0;
}

Stack Visibility: While stacks are separate, its possible for one thread to access another's stack memory if it obtains a pointer, though this is generally unsafe.

Thread-Local Storage

Global variables are shared across all threads of a process. To create a global variable unique to each thread, use the __thread storage class specifier.

#include <iostream>
#include <pthread.h>
#include <vector>
__thread int per_thread_counter = 0;
void* increment_a(void*) {
    per_thread_counter += 50;
    std::cout << "Address: " << &per_thread_counter << ", Value: " << per_thread_counter << std::endl;
    return nullptr;
}
void* increment_b(void*) {
    per_thread_counter += 100;
    std::cout << "Address: " << &per_thread_counter << ", Value: " << per_thread_counter << std::endl;
    return nullptr;
}
int main() {
    std::vector<pthread_t> workers;
    pthread_t id;
    pthread_create(&id, NULL, increment_a, NULL);
    workers.push_back(id);
    pthread_create(&id, NULL, increment_b, NULL);
    workers.push_back(id);
    for (auto& worker : workers) {
        pthread_join(worker, NULL);
    }
    return 0;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.