ADVERTISEMENT
Responsive Ad Unit • Systems Architecture & Low-Level Engineering

Threads & Multithreading in Operating Systems

Learn how modern operating systems execute multiple tasks efficiently using threads. Understand the difference between processes and threads, why multithreading improves performance, and how Linux implements threads using POSIX Threads (Pthreads).

Learning Objectives

  • Understand what a thread is.
  • Learn why threads are needed.
  • Differentiate between processes and threads.
  • Understand thread life cycle.
  • Learn the benefits of multithreading.
  • Prepare for Linux Pthreads programming.

1. What is a Thread?

A thread is the smallest unit of CPU execution within a process. Every process contains at least one thread. When the operating system schedules work for the CPU, it schedules threads rather than the process as a whole.

Process | +----------------------+ | Thread 1 | +----------------------+ | CPU Executes

A thread performs the actual execution of program instructions.

2. Why Are Threads Needed?

Many applications need to perform multiple activities at the same time. Examples include:

  • A web browser downloading data while remaining responsive.
  • A media player decoding audio while updating the user interface.
  • A web server handling multiple client requests concurrently.
  • An IDE compiling code while allowing the user to continue editing.

Instead of creating multiple processes, applications often create multiple threads within the same process.

3. Single-Threaded Process

+------------------------------------+ | Process | | | | Thread | | | +------------------------------------+

Only one thread executes instructions. If this thread blocks while waiting for I/O, the process cannot execute additional application code until the thread becomes runnable again.

4. Multithreaded Process

+-----------------------------------------------+ | Process | | | | Thread 1 | | Thread 2 | | Thread 3 | | Thread 4 | | | +-----------------------------------------------+

Multiple threads belong to the same process. The operating system may schedule these threads independently. On multicore systems, multiple threads from the same process may execute simultaneously on different CPU cores.

5. Process vs Thread

Process Thread
Independent execution environment Execution unit inside a process
Own virtual address space Shares the process address space
Higher creation overhead Lower creation overhead
Separate resources Shares most process resources
Inter-process communication required Direct communication through shared memory

6. Resources Shared by Threads

Threads belonging to the same process share many resources.

  • Virtual address space
  • Global variables
  • Heap memory
  • Open file descriptors
  • Signal dispositions
  • Current working directory

7. Resources Not Shared by Threads

Each thread maintains its own execution context.

  • Program Counter (Instruction Pointer)
  • CPU Registers
  • Stack
  • Thread ID
  • Scheduling information
  • Thread-local storage

8. Why Does Every Thread Have Its Own Stack?

Each thread executes its own sequence of function calls. Local variables, return addresses, and function parameters are stored on the stack. If multiple threads shared a single stack, they would overwrite one another's execution data. Therefore, every thread has an independent stack.

Process | +---------------------------+ | Shared Heap | | Shared Globals | | Shared Files | +---------------------------+ | +---------------------------+ | Thread 1 Stack | +---------------------------+ | +---------------------------+ | Thread 2 Stack | +---------------------------+ | +---------------------------+ | Thread 3 Stack | +---------------------------+

9. Key Points

  • A thread is the smallest schedulable execution unit.
  • Every process contains at least one thread.
  • Threads inside a process share most resources.
  • Each thread has its own stack and CPU context.
  • Multithreading improves responsiveness and resource utilization.

10. Interview Questions

  1. What is a thread?
  2. Why are threads lighter than processes?
  3. Which resources are shared among threads?
  4. Why does each thread require its own stack?
  5. Can two threads execute simultaneously on different CPU cores?

11. Thread Life Cycle

Like a process, a thread passes through different execution states during its lifetime.

Thread Created ↓ Ready ↓ Running ↓ Blocked (Waiting) ↓ Ready ↓ Running ↓ Terminated

The operating system scheduler moves threads between these states based on CPU availability, synchronization, and I/O events.

12. POSIX Threads (Pthreads)

Linux provides thread programming through the POSIX Threads (Pthreads) library. Pthreads is the standard API defined by POSIX for creating and managing threads in C programs.

Most multithreaded Linux applications use the pthread library.

13. Creating a Thread

A new thread is created using the pthread_create() function.

int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg );
Parameter Description
thread Receives the thread identifier.
attr Thread attributes (NULL for default).
start_routine Function executed by the new thread.
arg Argument passed to the thread function.

14. Execution Flow of pthread_create()

Main Thread ↓ pthread_create() ↓ Kernel Creates New Thread ↓ Main Thread Continues ↓ New Thread Starts Executing

After successful creation, both the original thread and the new thread may execute concurrently. The scheduler determines when each thread runs.

15. First Pthread Program

#include <stdio.h> #include <pthread.h> void *worker(void *arg) { printf("Hello from worker thread\n"); return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, worker, NULL); pthread_join(tid, NULL); printf("Main thread finished\n"); return 0; }

16. Program Execution

main() ↓ pthread_create() ↓ Worker Thread Starts ↓ Worker Prints Message ↓ Worker Returns ↓ pthread_join() ↓ Main Continues ↓ Program Ends

17. Compiling Pthread Programs

Compile a pthread program using:

gcc thread.c -pthread -o thread

Run the executable:

./thread
The -pthread option enables thread support during both compilation and linking.

18. Thread Termination

A thread may terminate in several ways:

  • Return from the thread start routine.
  • Call pthread_exit().
  • Be cancelled by another thread.
  • The process terminates.

A simple thread exit:

pthread_exit(NULL);

19. Waiting for a Thread

The pthread_join() function waits for another thread to finish execution.

int pthread_join( pthread_t thread, void **retval );

The calling thread blocks until the specified thread terminates.

Main Thread ↓ pthread_join() ↓ Wait ↓ Worker Finishes ↓ Main Continues

20. Key Points

  • Linux uses the POSIX Threads (Pthreads) API.
  • pthread_create() creates a new thread.
  • pthread_join() waits for a thread to finish.
  • pthread_exit() terminates the calling thread.
  • The scheduler determines when each thread executes.

21. Interview Questions

  1. What is the purpose of pthread_create()?
  2. Why is pthread_join() needed?
  3. What happens if the main thread exits before worker threads finish?
  4. What are the parameters of pthread_create()?
  5. Why should Linux programs be compiled with -pthread?

22. Thread Identifier (pthread_t)

Each POSIX thread has a unique thread identifier represented by the pthread_t data type. This identifier allows a program to refer to a specific thread when performing operations such as waiting for its completion or comparing it with another thread.

pthread_t tid;

The actual representation of pthread_t is implementation dependent and should be treated as an opaque type.

23. pthread_self()

The pthread_self() function returns the identifier of the calling thread.

pthread_t tid = pthread_self();
Running Thread ↓ pthread_self() ↓ Returns Its Own Thread ID

24. pthread_equal()

Because pthread_t is an opaque type, thread identifiers should be compared using pthread_equal() instead of the == operator.

if (pthread_equal(t1, t2)) { printf("Same thread\n"); }
Using pthread_equal() makes the program portable across POSIX implementations.

25. Joinable Threads

By default, a newly created POSIX thread is joinable. A joinable thread retains its termination information until another thread calls pthread_join().

Thread Starts ↓ Thread Finishes ↓ Termination Information Retained ↓ pthread_join() ↓ Resources Released

26. Detached Threads

A detached thread releases its resources automatically when it terminates. No other thread can successfully wait for a detached thread using pthread_join().

Detached Thread ↓ Executes ↓ Terminates ↓ Resources Automatically Released

27. pthread_detach()

A joinable thread can be converted into a detached thread by calling pthread_detach().

pthread_detach(thread_id);

After a thread is detached, it cannot be joined later.

28. Returning a Value from a Thread

A thread may return a value either by returning from its start routine or by calling pthread_exit(). The value can later be obtained using pthread_join().

void *worker(void *arg) { static int result = 100; return &result; }

29. Receiving the Return Value

void *retval; pthread_join(thread, &retval); printf("%d\n", *(int *)retval);

The second argument of pthread_join() receives the value returned by the terminated thread.

30. Creating Multiple Threads

#include #include void *worker(void *arg) { printf("Thread %ld running\n", (long)arg); return NULL; } int main() { pthread_t tid[3]; for (long i = 0; i < 3; i++) pthread_create(&tid[i], NULL, worker, (void *)i); for (int i = 0; i < 3; i++) pthread_join(tid[i], NULL); return 0; }
Main Thread ↓ Create T1 ↓ Create T2 ↓ Create T3 ↓ Scheduler Executes Threads ↓ Join T1 ↓ Join T2 ↓ Join T3 ↓ Program Ends

31. Key Points

  • pthread_t identifies a POSIX thread.
  • pthread_self() returns the calling thread's identifier.
  • pthread_equal() is the portable way to compare thread IDs.
  • Threads are joinable by default.
  • Detached threads automatically release their resources.
  • pthread_join() retrieves a thread's termination status.

32. Interview Questions

  1. What is the purpose of pthread_t?
  2. Why should pthread_equal() be used instead of ==?
  3. What is the difference between joinable and detached threads?
  4. Can a detached thread be joined?
  5. How can a thread return a value to another thread?

33. Race Condition

A race condition occurs when multiple threads access the same shared data concurrently and at least one thread modifies that data without proper synchronization. The final program result may depend on the order in which the threads execute.

Thread A | v Shared Variable ^ | Thread B
Race conditions often produce unpredictable and inconsistent program behavior.

34. Race Condition Example

Suppose two threads increment the same counter.

counter = counter + 1;

This statement appears simple but is typically executed as multiple CPU operations:

Read counter ↓ Increment Value ↓ Write Updated Value

If another thread executes these operations at the same time, one update may overwrite the other, resulting in an incorrect final value.

35. Critical Section

A critical section is a portion of code that accesses shared resources such as variables, files, or shared memory. Only one thread should execute a critical section at a time when concurrent access could lead to incorrect results.

Thread A ↓ Critical Section ↓ Thread B Waits

36. Mutual Exclusion (Mutex)

Mutual exclusion ensures that only one thread at a time can execute a critical section. POSIX threads provide mutexes for implementing mutual exclusion.

Thread A ↓ Lock Mutex ↓ Critical Section ↓ Unlock Mutex ↓ Thread B Executes

37. pthread_mutex_t

A mutex object is represented by the pthread_mutex_t data type.

pthread_mutex_t mutex;

Before a mutex is used, it must be initialized.

38. Initializing a Mutex

A mutex may be initialized dynamically using pthread_mutex_init().

pthread_mutex_init(&mutex, NULL);

The second parameter specifies mutex attributes. Passing NULL uses the default attributes.

39. Locking a Mutex

A thread enters a protected critical section by locking the mutex.

pthread_mutex_lock(&mutex);
Mutex Available ↓ Acquire Lock ↓ Critical Section

If another thread already holds the mutex, the calling thread blocks until the mutex becomes available.

40. Unlocking a Mutex

After completing the critical section, the thread releases the mutex.

pthread_mutex_unlock(&mutex);
Critical Section Finished ↓ Unlock Mutex ↓ Another Waiting Thread May Proceed

41. First Mutex Program

#include #include int counter = 0; pthread_mutex_t mutex; void *worker(void *arg) { pthread_mutex_lock(&mutex); counter++; pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t t1, t2; pthread_mutex_init(&mutex, NULL); pthread_create(&t1, NULL, worker, NULL); pthread_create(&t2, NULL, worker, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("Counter = %d\n", counter); pthread_mutex_destroy(&mutex); return 0; }

42. Mutex Execution Flow

Thread 1 ↓ Lock Mutex ↓ Increment Counter ↓ Unlock Mutex ↓ Thread 2 ↓ Lock Mutex ↓ Increment Counter ↓ Unlock Mutex

Because only one thread holds the mutex at a time, the shared counter is updated safely.

43. Key Points

  • A race condition occurs when multiple threads access shared data without proper synchronization.
  • A critical section accesses shared resources.
  • Mutexes provide mutual exclusion.
  • pthread_mutex_lock() acquires a mutex.
  • pthread_mutex_unlock() releases the mutex.
  • A mutex should be destroyed using pthread_mutex_destroy() when it is no longer needed.

44. Interview Questions

  1. What is a race condition?
  2. What is a critical section?
  3. Why is a mutex required?
  4. What happens if two threads modify the same variable without synchronization?
  5. What is the purpose of pthread_mutex_lock()?
  6. Why should a mutex be unlocked?
  7. When should pthread_mutex_destroy() be called?

45. Static Mutex Initialization

Instead of calling pthread_mutex_init(), a mutex with default attributes can be initialized statically using PTHREAD_MUTEX_INITIALIZER.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Static initialization is commonly used for global and static mutexes.

46. Mutex Attributes

Mutex behavior can be customized using mutex attributes. Applications first initialize a pthread_mutexattr_t object, configure its properties, and then create a mutex using those attributes.

Initialize Attributes ↓ Configure Attributes ↓ Create Mutex ↓ Use Mutex

47. pthread_mutex_trylock()

Unlike pthread_mutex_lock(), which waits until the mutex becomes available, pthread_mutex_trylock() attempts to acquire the mutex immediately.

if (pthread_mutex_trylock(&mutex) == 0) { /* Critical section */ pthread_mutex_unlock(&mutex); } else { /* Mutex currently unavailable */ }
If another thread already owns the mutex, the function returns immediately with an error instead of blocking.

48. Blocking vs Non-blocking Locking

Function Behavior
pthread_mutex_lock() Blocks until the mutex becomes available.
pthread_mutex_trylock() Returns immediately if the mutex is unavailable.

49. Deadlock

A deadlock occurs when two or more threads wait indefinitely for resources held by one another. As a result, none of the waiting threads can continue execution.

Thread A ↓ Locks Mutex 1 ↓ Needs Mutex 2 ===================== Thread B ↓ Locks Mutex 2 ↓ Needs Mutex 1 ===================== Both Threads Wait Forever

50. Deadlock Example

/* Thread A */ pthread_mutex_lock(&mutex1); /* ... */ pthread_mutex_lock(&mutex2); /* Thread B */ pthread_mutex_lock(&mutex2); /* ... */ pthread_mutex_lock(&mutex1);

If both threads acquire their first mutex before requesting the second, each waits indefinitely for the other to release its mutex.

51. Preventing Deadlocks

Common techniques for reducing the likelihood of deadlocks include:

  • Acquire multiple mutexes in a consistent order.
  • Release locks as soon as possible.
  • Avoid holding locks longer than necessary.
  • Reduce the number of simultaneously held locks.
  • Use pthread_mutex_trylock() when appropriate to avoid waiting indefinitely.

52. Lock Ordering

One of the most effective deadlock prevention strategies is to acquire multiple mutexes in the same order throughout the program.

Correct Mutex A ↓ Mutex B ↓ Unlock B ↓ Unlock A Never Mutex B ↓ Mutex A

Consistent lock ordering prevents circular waiting among threads.

53. Multiple Mutex Example

pthread_mutex_lock(&mutexA); pthread_mutex_lock(&mutexB); /* Critical section */ pthread_mutex_unlock(&mutexB); pthread_mutex_unlock(&mutexA);

Every thread in the program should follow the same lock acquisition order.

54. Key Points

  • PTHREAD_MUTEX_INITIALIZER provides static initialization.
  • pthread_mutex_trylock() never waits for a mutex.
  • Deadlocks occur when threads wait indefinitely for one another.
  • Consistent lock ordering is a common deadlock prevention technique.
  • Programs should minimize the time mutexes remain locked.

55. Interview Questions

  1. When should PTHREAD_MUTEX_INITIALIZER be used?
  2. How does pthread_mutex_trylock() differ from pthread_mutex_lock()?
  3. What is a deadlock?
  4. Why does inconsistent lock ordering cause deadlocks?
  5. Name three techniques for preventing deadlocks.
  6. Why should critical sections be kept as short as possible?

56. Condition Variables

A condition variable allows threads to wait until a particular condition becomes true. Unlike a mutex, a condition variable does not protect shared data by itself. It is always used together with a mutex.

Shared Data ↓ Mutex Protects Data ↓ Condition Variable Waits ↓ Condition Becomes True ↓ Waiting Thread Continues

57. pthread_cond_t

A POSIX condition variable is represented by the pthread_cond_t type.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

It may also be initialized dynamically using pthread_cond_init().

58. pthread_cond_wait()

A thread waits for a condition using pthread_cond_wait().

pthread_cond_wait(&cond, &mutex);

This function performs three operations atomically:

  1. Releases the mutex.
  2. Puts the calling thread to sleep.
  3. Reacquires the mutex before returning.
Lock Mutex ↓ Condition False ↓ pthread_cond_wait() ↓ Mutex Released ↓ Thread Sleeps ↓ Signal Received ↓ Mutex Reacquired ↓ Continue Execution

59. pthread_cond_signal()

When one thread changes the shared state so that a waiting condition may now be true, it can wake one waiting thread using pthread_cond_signal().

pthread_cond_signal(&cond);
Producer Updates Data ↓ Signal Condition ↓ One Waiting Thread Wakes

60. pthread_cond_broadcast()

To wake all threads waiting on a condition variable, use pthread_cond_broadcast().

pthread_cond_broadcast(&cond);
Condition Changes ↓ Broadcast ↓ Thread A Wakes ↓ Thread B Wakes ↓ Thread C Wakes

61. Producer–Consumer Synchronization

A classic application of condition variables is the Producer–Consumer problem.

Producer ↓ Shared Buffer ↓ Consumer

The producer places data into the shared buffer. If the buffer is full, the producer waits. The consumer removes data from the buffer. If the buffer is empty, the consumer waits.

62. Producer–Consumer Execution Flow

Producer ↓ Lock Mutex ↓ Buffer Full? ↓ Yes → Wait ↓ No → Insert Item ↓ Signal Consumer ↓ Unlock Mutex Consumer ↓ Lock Mutex ↓ Buffer Empty? ↓ Yes → Wait ↓ No → Remove Item ↓ Signal Producer ↓ Unlock Mutex

63. Simplified Producer–Consumer Example

pthread_mutex_lock(&mutex); while (buffer_empty) { pthread_cond_wait(&cond, &mutex); } remove_item(); pthread_mutex_unlock(&mutex);

The waiting thread repeatedly checks the condition after waking before continuing.

64. Why Use a while Loop?

A thread should check the condition inside a while loop rather than an if statement.

  • A thread may wake even though the condition is still false.
  • Another thread may consume the shared resource before the awakened thread acquires the mutex.
  • Rechecking the condition ensures correct synchronization.
Wake Up ↓ Reacquire Mutex ↓ Condition True? ↓ No ↓ Wait Again

65. Key Points

  • Condition variables synchronize threads based on program state.
  • A condition variable is always used together with a mutex.
  • pthread_cond_wait() releases the mutex while waiting and reacquires it before returning.
  • pthread_cond_signal() wakes one waiting thread.
  • pthread_cond_broadcast() wakes all waiting threads.
  • The waiting condition should be checked inside a while loop.

66. Interview Questions

  1. What problem do condition variables solve?
  2. Why must a condition variable be used with a mutex?
  3. What does pthread_cond_wait() do atomically?
  4. What is the difference between pthread_cond_signal() and pthread_cond_broadcast()?
  5. Why should a waiting thread use a while loop instead of an if statement?
  6. How are condition variables used in the Producer–Consumer problem?

67. Read–Write Locks

A Read–Write Lock allows multiple threads to read shared data simultaneously while ensuring that only one thread can modify the data at a time.

Read–Write locks improve performance when read operations occur much more frequently than write operations.

Shared Data ↓ Read–Write Lock ↓ Multiple Readers ↓ Single Writer

68. Reader–Writer Rules

Operation Allowed?
Multiple Readers Yes
Reader + Writer No
Multiple Writers No

69. pthread_rwlock_t

A POSIX Read–Write Lock is represented by the pthread_rwlock_t type.

pthread_rwlock_t rwlock;

The lock must be initialized before use.

pthread_rwlock_init(&rwlock, NULL);

70. pthread_rwlock_rdlock()

A thread acquires a read lock using pthread_rwlock_rdlock().

pthread_rwlock_rdlock(&rwlock);

Multiple threads may hold read locks simultaneously, provided that no thread currently holds a write lock.

Reader 1 ↓ Read Lock Reader 2 ↓ Read Lock Reader 3 ↓ Read Lock

71. pthread_rwlock_wrlock()

A thread acquires exclusive access using pthread_rwlock_wrlock().

pthread_rwlock_wrlock(&rwlock);

While a write lock is held, no other thread may obtain either a read or write lock.

Writer ↓ Write Lock ↓ Exclusive Access

72. pthread_rwlock_unlock()

Both read and write locks are released using the same function.

pthread_rwlock_unlock(&rwlock);

73. Read–Write Lock Example

/* Reader */ pthread_rwlock_rdlock(&rwlock); /* Read shared data */ pthread_rwlock_unlock(&rwlock); /* Writer */ pthread_rwlock_wrlock(&rwlock); /* Modify shared data */ pthread_rwlock_unlock(&rwlock);

74. Spin Locks

A spin lock is a synchronization mechanism in which a thread repeatedly checks whether a lock has become available instead of sleeping while waiting.

Try Lock ↓ Busy Waiting ↓ Lock Available? ↓ Yes ↓ Acquire Lock

Spin locks are generally suitable only when the expected wait time is very short.

75. pthread_spinlock_t

pthread_spinlock_t spin; pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE); pthread_spin_lock(&spin); /* Critical Section */ pthread_spin_unlock(&spin); pthread_spin_destroy(&spin);

Unlike a mutex, a waiting thread actively consumes CPU time while spinning.

76. Thread Barriers

A barrier synchronizes multiple threads by requiring them all to reach the same execution point before any of them continue.

Thread A ↓ Barrier Thread B ↓ Barrier Thread C ↓ Barrier ↓ All Continue Together

77. pthread_barrier_t

pthread_barrier_t barrier; pthread_barrier_init(&barrier, NULL, 4); /* Work */ pthread_barrier_wait(&barrier); /* Continue after all 4 threads arrive */ pthread_barrier_destroy(&barrier);

The barrier count specifies the number of participating threads that must reach the barrier before any waiting thread proceeds.

78. Key Points

  • Read–Write locks allow concurrent readers but only one writer.
  • Readers cannot access shared data while a writer holds the write lock.
  • Spin locks use busy waiting instead of sleeping.
  • Spin locks are suitable only for short waiting periods.
  • Barriers synchronize groups of threads at predefined execution points.

79. Interview Questions

  1. When should a Read–Write Lock be used instead of a mutex?
  2. Can multiple readers hold a read lock simultaneously?
  3. Can a writer execute while readers hold the lock?
  4. What is the difference between a mutex and a spin lock?
  5. Why do spin locks consume CPU time?
  6. What is the purpose of a thread barrier?
  7. What does the barrier count represent?

80. Thread-Specific Data (TSD)

Threads in the same process share global variables. However, there are situations where each thread needs its own private copy of a variable. POSIX provides Thread-Specific Data (TSD), also known as Thread-Local Storage (TLS), for this purpose.

Process │ ├── Thread 1 → Private Data ├── Thread 2 → Private Data └── Thread 3 → Private Data

81. pthread_key_t

A thread-specific data key is represented using the pthread_key_t type. Each thread can associate its own value with the same key.

pthread_key_t key;

82. pthread_key_create()

Create a thread-specific key using:

pthread_key_create(&key, NULL);

The optional second argument specifies a destructor function that is called automatically when a thread exits and has a non-NULL value associated with the key.

83. pthread_setspecific()

int value = 100; pthread_setspecific(key, &value);

Each thread stores its own value independently, even though all threads use the same key.

84. pthread_getspecific()

int *value; value = pthread_getspecific(key);

The returned pointer corresponds to the value associated with the calling thread.

85. Thread Cleanup Handlers

Cleanup handlers allow a thread to automatically release resources when it exits or is cancelled. Typical cleanup tasks include:

  • Unlocking mutexes
  • Closing files
  • Freeing dynamically allocated memory
  • Releasing other system resources

86. pthread_cleanup_push()

pthread_cleanup_push(cleanup_function, argument); /* Protected code */ pthread_cleanup_pop(1);

The cleanup handler is executed when the thread exits or when pthread_cleanup_pop() is called with a non-zero argument.

87. Thread Cancellation

One thread can request cancellation of another thread using pthread_cancel().

pthread_cancel(thread);
Thread A ↓ pthread_cancel() ↓ Cancellation Request ↓ Target Thread

Cancellation is a request. The target thread responds according to its cancellation state and cancellation type.

88. Cancellation Points

A thread normally acts on a cancellation request when it reaches a cancellation point. Many blocking POSIX functions, such as waiting on a condition variable or reading from a file descriptor, are cancellation points.

Running Thread ↓ Cancellation Requested ↓ Cancellation Point Reached ↓ Cleanup Handlers Execute ↓ Thread Terminates

89. Thread-Specific Data Example

pthread_key_t key; void *worker(void *arg) { int value = (int)(long)arg; pthread_setspecific(key, &value); printf("%d\n", *(int *)pthread_getspecific(key)); return NULL; }

Each thread prints the value stored in its own thread-specific storage.

90. Key Points

  • Thread-specific data provides private storage for each thread.
  • pthread_key_create() creates a thread-specific key.
  • pthread_setspecific() stores data for the calling thread.
  • pthread_getspecific() retrieves the calling thread's stored data.
  • Cleanup handlers release resources during thread termination.
  • pthread_cancel() requests cancellation of another thread.
  • Cancellation normally occurs at defined cancellation points.

91. Interview Questions

  1. Why is Thread-Specific Data needed?
  2. What is the purpose of pthread_key_t?
  3. What is the role of the destructor passed to pthread_key_create()?
  4. What is the difference between global variables and thread-specific data?
  5. What is a cleanup handler?
  6. Why are cleanup handlers important in multithreaded programs?
  7. Does pthread_cancel() immediately terminate a thread?
  8. What is a cancellation point?

92. Thread Attributes (pthread_attr_t)

POSIX allows various properties of a thread to be configured before the thread is created. These properties are stored in a pthread_attr_t object.

pthread_attr_t attr; pthread_attr_init(&attr);

If no custom attributes are required, NULL may be passed as the attribute argument to pthread_create().

93. Creating a Thread with Attributes

pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_create(&tid, &attr, worker, NULL); pthread_attr_destroy(&attr);

After thread creation, the attribute object is no longer needed and should be destroyed.

94. Detached Thread Attribute

Instead of creating a joinable thread and later calling pthread_detach(), a thread may be created in the detached state.

pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
Initialize Attributes ↓ Set Detached State ↓ Create Thread ↓ Resources Released Automatically

95. Configuring Thread Stack Size

The default stack size is sufficient for many applications. Some applications that perform deep recursion or allocate large local objects may require a larger stack.

pthread_attr_setstacksize( &attr, 1024 * 1024 );

The example above requests a stack size of approximately 1 MB.

96. Thread Scheduling Attributes

POSIX thread attributes can also specify scheduling-related properties, such as scheduling policy and priority. Actual behavior depends on operating-system support and user privileges.

Thread Attributes ↓ Scheduling Policy ↓ Priority ↓ Thread Creation

97. Best Practices for Multithreaded Programs

  • Protect shared data with appropriate synchronization.
  • Keep critical sections as short as possible.
  • Acquire multiple locks in a consistent order.
  • Release resources promptly.
  • Prefer condition variables over busy waiting when threads need to wait.
  • Join or detach every created thread.
  • Check the return values of POSIX thread functions.
  • Minimize shared mutable state where practical.

98. Common Programming Mistakes

Mistake Possible Result
Accessing shared data without synchronization Race conditions
Forgetting to unlock a mutex Deadlock or blocked threads
Returning the address of a local variable from a thread Undefined behavior
Ignoring return values Hidden runtime failures
Using excessive locking Reduced concurrency and performance
Not joining or detaching threads Thread resources remain allocated

99. Complete Chapter Summary

  • A thread is the smallest schedulable unit of execution.
  • Threads within a process share most resources but maintain separate execution contexts.
  • POSIX Threads (Pthreads) provide a portable API for multithreaded programming.
  • Mutexes protect critical sections.
  • Condition variables synchronize threads based on program state.
  • Read–Write locks improve performance for read-dominant workloads.
  • Spin locks use busy waiting and are appropriate only for short wait times.
  • Barriers synchronize groups of threads.
  • Thread-specific data provides private storage for each thread.
  • Cleanup handlers assist with resource management during thread termination.
  • Thread attributes configure behavior before thread creation.

100. Comprehensive Interview Questions

  1. What is the difference between a process and a thread?
  2. Which resources are shared among threads?
  3. Why does each thread require its own stack?
  4. Explain the purpose of pthread_create().
  5. What is the difference between joinable and detached threads?
  6. What is a race condition?
  7. How does a mutex prevent race conditions?
  8. Why should pthread_cond_wait() be used inside a while loop?
  9. When should a Read–Write Lock be preferred over a mutex?
  10. What is a deadlock, and how can it be avoided?
  11. What is Thread-Specific Data?
  12. What is the purpose of cleanup handlers?
  13. What is the difference between pthread_mutex_lock() and pthread_mutex_trylock()?
  14. Why are barriers useful?
  15. What are common mistakes in multithreaded programming?

Next Chapter → Interprocess Communication (IPC)

The next module explains how independent processes communicate and synchronize with one another. Topics include:

  • Why IPC is required
  • Process communication models
  • Pipes
  • Named Pipes (FIFO)
  • Message Queues
  • Shared Memory
  • Semaphores
  • Sockets
  • Memory-Mapped Files
  • POSIX IPC
  • System V IPC
  • IPC comparison
  • Linux IPC examples
Knowledge Check

Concept Mastery Assessment — Module 19

Verify your conceptual understanding with 12 targeted technical assessment questions. Instant feedback and explanations are provided for every choice.

Question 1 of 12
What is the primary architectural purpose of Threads & Multithreading Architecture in operating systems?
Explanation
Threads & Multithreading Architecture is designed to manage kernel threads vs user threads (n:1, 1:1, m:n models), thread control block (tcb), thread creation cost vs processes, and thread safety. within the operating system architecture.
Question 2 of 12
Which system interface or data structure is centrally associated with Threads & Multithreading Architecture?
Explanation
Under POSIX and Linux, Threads & Multithreading Architecture operates through standardized system calls and kernel control structures.
Question 3 of 12
What safety or concurrency constraint must developers consider when implementing Threads & Multithreading Architecture?
Explanation
Proper synchronization, avoidance of race conditions, and handling reentrant execution are critical when working with Threads & Multithreading Architecture.
Question 4 of 12
When a system call related to Threads & Multithreading Architecture fails in C, how does the POSIX runtime conventionally report the failure?
Explanation
Standard POSIX system calls return -1 or NULL on error, assigning an error integer (e.g. EINVAL, EPERM, EAGAIN) to the thread-local errno variable.
Question 5 of 12
What is the primary performance tradeoff associated with Threads & Multithreading Architecture?
Explanation
Operations involving Threads & Multithreading Architecture balance performance against kernel transition, memory bandwidth, and cache locality tradeoffs.
Question 6 of 12
What security principle governs access to resources in Threads & Multithreading Architecture?
Explanation
The kernel validates credentials, effective permissions, and Linux capabilities before granting access to protected operations.
Question 7 of 12
How does execution of Threads & Multithreading Architecture transition between User Space and Kernel Space?
Explanation
User space transitions to kernel mode securely via software traps or SYSCALL instructions that switch processor rings to Ring 0.
Question 8 of 12
What happens to resources allocated by Threads & Multithreading Architecture when the owning process terminates?
Explanation
Process resources (open fds, memory maps) are automatically reclaimed by the kernel on termination, while kernel-persistent objects survive.
Question 9 of 12
In production Linux server environments, how is Threads & Multithreading Architecture optimized for high throughput?
Explanation
Linux optimizes throughput via zero-copy data paths, asynchronous kernel events, and scalable concurrency primitives.
Question 10 of 12
What standard governs the portability of Threads & Multithreading Architecture across Unix, Linux, and macOS platforms?
Explanation
IEEE POSIX standards define consistent APIs across Unix-like platforms ensuring portable source code execution.
Question 11 of 12
Which command-line diagnostic tool is most effective for inspecting and troubleshooting Threads & Multithreading Architecture at runtime?
Explanation
Utilities like strace, lsof, and the /proc filesystem provide direct visibility into kernel interactions and resource status.
Question 12 of 12
What is a critical production best practice when developing systems software involving Threads & Multithreading Architecture?
Explanation
Defensive programming requires explicit error checking on all system operations, proper resource cleanup, and avoiding memory corruption.
0 / 12 (0%)
Great job! Review the concepts above and proceed when ready.
ADVERTISEMENT
Responsive Ad Unit • Technical Courses & Certifications