POSIX Threads in OS


The POSIX thread standard is followed by POSIX threads, sometimes referred to as pthreads. A program may be made parallel by using threads, which divide a single job into a number of separate ones that can run simultaneously. Threads in operating systems can either be user-level or kernel-level and are handled by the kernel. While the operating system manages kernel-level threads, user-level threads are totally controlled by the application. Kernel-level threads include POSIX threads.

A thread creation and manipulation API is defined by the POSIX thread standard. The methods in this API allow you to start new threads, modify thread properties, synchronize threads, and end threads. In many Unix-like operating systems, including Linux, macOS, and FreeBSD, as well as in certain other operating systems, POSIX threads are employed. Because of their standardized API and effective implementation, they are a well-liked option for creating multi-threaded programs.

The Concept of Thread

A thread is often a quick procedure that may run in parallel with other threads inside the same process. Global and static variables, heap memory, and the stack are all shared by all threads. Although they can share heap memory and stack, threads have their own stack space. Message forwarding and shared memory are two ways that threads can communicate with one another.

Example Code for POSIX Thread in C Language

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
#define NUM_THREADS 5
 
void *PrintHello(void *threadid) {
	long tid;
	tid = (long)threadid;
	printf("Hello World! It's me, thread #%ld!\n", tid);
	pthread_exit(NULL);
}
 
int main(int argc, char *argv[]) {
	pthread_t threads[NUM_THREADS];
	int rc;
	long t;
 
	for (t = 0; t < NUM_THREADS; t++) {
    	printf("In main: creating thread %ld
", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc) { printf("ERROR; return code from pthread_create() is %d
", rc); exit(-1); } } pthread_exit(NULL); }

Output

In main: creating thread 0
In main: creating thread 1
Hello World! It's me, thread #0!
In main: creating thread 2
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #4!
Hello World! It's me, thread #2!
Hello World! It's me, thread #1!
Hello World! It's me, thread #3!

In this example, NUM_THREADS threads are created, each of which prints a message. The thread is started using the PrintHello method. The PrintHello function is used as the start routine and pthread_create is used to create the threads in the main function. Then, we use pthread_exit to wait for each thread to complete its task.

We convert the threadid input to a long data type in the PrintHello method and use it to print a message identifying the thread that is publishing the message. To end the thread, we finally call pthread_exit.

The pthread_create function requires four arguments: the thread start routine (in this case, PrintHello), the thread ID, a pointer to a pthread_t variable that will hold the new thread's ID, a pointer to a pthread_attr_t variable that specifies thread attributes (we pass NULL to use default attributes), and a void pointer that is passed to the start routine.

If pthread_create returns a non-zero number, an error occurred when creating the thread. In this instance, we output an error message and perform a non-zero status code program exit.

It should be noted that the threads may not run in a specific order. The output messages may seem written in a different order each time you run this program.

You must link the program with the pthread library using the -pthread option in order to build and link it −

$ gcc -pthread example.c -o example

This program will generate NUM_THREADS threads, each of which will print a message with a thread identification. When all of the threads have finished running, the program ends.

Managing the Threads

To manage a thread, a variety of functions are available, including −

  • Use pthread join to wait for a thread to finish (). The thread ID of the thread to join and a reference to a variable that will be used to store the thread's exit status is the function's two inputs.

  • The second technique, pthread detach, allows a thread to be separated from its parent thread (). When a thread terminates after being disconnected, resources are immediately freed.

  • To terminate a thread, use the pthread_cancel() function. When a thread is canceled, its execution terminates immediately.

Thread Synchronization

To prevent race situations and other concurrency-related problems, thread synchronization is crucial in multi-threaded programming. Pthreads offers a number of synchronization options, including −

Mutexes

To prevent concurrent access to shared resources, mutexes are utilized. A mutex offers mutual exclusion, which limits the number of threads that can simultaneously hold the mutex. A mutex is acquired and released using the pthread mutex lock() and pthread mutex unlock() methods, respectively.

Condition variables

These variables serve as alerts and watchwords for events. A mutex is connected to a condition variable, and the mutex needs to be locked before the condition variable may be waited on. To wait for and signal a condition variable, use the pthread_cond_wait() and pthread_cond_signal() methods, respectively.

Semaphores

Use semaphores to manage access to a shared resource. A semaphore keeps track of a counter that threads can increase or decrease. Threads waiting on the semaphore are halted when the counter approaches 0 and must wait until it is increased. Synchronization primitives are initialized, destroyed, waited on, and signaled using the pthread_mutex_init(), pthread_mutex_destroy(), pthread_cond_init(), pthread_cond_destroy(), sem_init(), sem_destroy(), wait(), and post() methods.

Thread Safety and Deadlocks

Multi-threaded programming requires thread safety, which makes ensuring that threads correctly access shared resources. Several threads can invoke a thread-safe function simultaneously without triggering race situations or other concurrency-related problems. Pthreads offers a number of ways to guarantee thread safety, including −

Mutexes

To prevent concurrent access to shared resources, mutexes are utilized. A thread can lock a shared resource using a mutex before using it and unlock it afterward. This makes sure that only one thread at a time may access the shared resource.

Atomic operations

Atomic operations are those that may be carried out in a single, uninterrupted step without the interference of other threads. A number of atomic operations are available in Pthreads, including atomic_add(), atomic_sub(), atomic_and(), atomic_or(), and atomic_xor ().

Thread-local storage

Data can be created using this technique, but it can only be accessed by the thread that created it. Without using global or static variables, such as thread IDs, this might be useful for storing thread-specific data.

Another problem that might arise in multi-threaded programming is deadlock. When two or more threads are waiting on one another to release a resource, a stalemate develops. Pthreads offers a number of ways to avoid deadlock, including −

Lock ordering

With lock ordering, you may make sure that locks are purchased in a particular sequence. Threads can stay clear of the cyclic dependencies that might result in deadlock by obtaining locks in the same sequence.

Timeout

Timeout is a method for unlocking locks after a certain period of time. Threads can prevent waiting endlessly for a lock to be released, which might result in deadlock, by employing a timeout.

Deadlock detection

This approach is used to identify and break deadlocks. Early deadlock detection allows threads to behave appropriately, such as releasing locks, to break the stalemate.

Debugging and Troubleshooting

Multi-threaded application debugging and troubleshooting can be difficult owing to threads' non-deterministic nature. Pthreads offers a number of tools for debugging and troubleshooting, including −

Thread-safe debugging

This method for troubleshooting multi-threaded applications prevents race situations and other concurrency-related problems. Many thread-safe debugging tools, including gdb and valgrind, are provided by pthreads.

Debugging tools

System calls and library calls made by threads may be traced using debugging tools like strace and trace. Developers can find problems like race situations, deadlocks, and performance bottlenecks by tracking the calls.

Logging and tracing

These methods are used to record details about how threads behave while they are being executed. Developers can spot problems like race situations, deadlocks, and performance bottlenecks as well as examine the behavior of threads as they execute by using logging and tracing.

Conclusion

A potent multi-threaded programming tool called Pthreads offers a number of techniques for managing threads, synchronizing threads, and ensuring thread safety. For the development of sophisticated multi-threaded applications, Pthreads offers a number of cutting-edge capabilities, including thread termination, thread-local storage, condition variables, and read-write locks. Pthreads can assist programmers in developing trustworthy and effective multi-threaded applications, despite the fact that building multi-threaded programs may be tough. By comprehending the principles and practices of multi-threaded programming, developers may fully utilize the capabilities of contemporary multi-core processors to design programs that give outstanding performance and scalability.

Updated on: 19-Jul-2023

405 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements