Threads

Threads

Threads are light-weight processes within a process. They share with other threads their code section, data section and OS resources like open files and signals.


Thread Concept πŸ’‘

Thread and process are similar concepts in operating systems. But, they are different in the following ways:

Process:

  • An instance of a program that is being executed.Β 
  • Need to make a separate system call fork() for each creation of a process to the OS.
  • Exists within its own address or memory space.
  • Independent and treated as an isolated process by the OS.

Thread:

  • A unit of execution (single sequence stream) within a process. It is a light-weight process.
  • Shares with other threads their code section, data section and OS resources like open files and signals.
  • Has its own program counter (PC), a register set, and a stack space.Β 
  • Be able to be scheduled by the operating system and run as independent entities within a process.
  • A process can have multiple threads.
thread in process
thread in process

The table below shows the characteristics of both process and thread: πŸ“‘

Characteristic Process Thread
Definition An instance of a running program. An execution path within a process.
Resources Has its own separate memory space, file descriptors, etc. Shares the resources (e.g., memory) of the parent process, but has its own stack, registers, etc.
Isolation Highly isolated; a crash in one process typically does not affect others. Less isolated within the same process; a thread crash can cause the entire process to crash.
Communication Inter-Process Communication (IPC) is more complex, requiring special mechanisms (pipes, message queues, etc.). Intra-process communication is simpler, as threads can directly access shared data.
Creation Overhead Creating a process is more expensive, as the OS needs to allocate new address spaces and other resources. Creating a thread is less expensive, as it does not need to duplicate most of the parent process’s resources.
Context Switching Context switching costs are higher, involving saving and restoring more state information. Context switching costs are lower, as only a smaller amount of state information needs to be saved and restored.
Scheduling Scheduled by the operating system scheduler. Can be scheduled by either the operating system or user-level libraries.
Multitasking On a single-core CPU, processes appear to run concurrently over time. On multi-core CPUs, they can run in parallel. More suitable for lightweight parallel or concurrent tasks within the same application.
Usage Suitable for running completely independent applications. Suitable for task decomposition within the same application to improve responsiveness and resource utilization.

Operation on Threads πŸ“

For coding with multiple threads, it is unlike Java, multithreading is not supported by C language standard. POSIX Threads is a POSIX standard for threads and its implementation is provided by the pthread library.

Tips: when compiling, the `pthread` in gcc/g++, add option `-lpthread` for linking.

Thread Creation

To create a thread, we can use the following function:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

Parameters:

  • thread: Pointer to an integer that will store the ID of the created thread.
  • attr: Thread attributes. It can be NULL if not used. We can specify PTHREAD_CREATE_JOINABLE if we want to join the thread after it is created.
  • start_routine: The function that will be executed by the thread. It is a pointer to the function that will be executed by the thread.
  • arg: The argument that will be passed to the function.

Return value:

  • 0 if the thread is created successfully. Otherwise, it returns an error code.
  • EINVAL if the start_routine is NULL.
  • EAGAIN if the maximum number of threads has been reached.
  • ENOMEM if there is not enough memory to create the thread.
  • EDEADLK if the thread is already created.

Thread Termination

To terminate a thread, use the following function:

void pthread_exit (void *value_ptr);

Parameters:

  • value_ptr: The value that will be returned by the thread when it is terminated. (NULL if not used.)

Additional notes:

  • This function is used to explicitly exit a thread. Typically, the pthread_exit() function is called after a thread has completed its work.
  • If main() finishes before the threads it has created, and exits with pthread_exit(), the other threads will continue to execute. Otherwise, they will be automatically terminated when main() finishes.
  • Use pthread_exit() to exit from all threads, especially main().

Thread Synchronization

Joining a thread is a blocking operation, which means that the thread will wait for the other thread to finish. We can use the following function:

int pthread_join(pthread_t thread, void *retval);

Parameters:

  • thread: The ID of the thread that will be joined.
  • retval: The value that will be returned by the joined thread. (NULL if not used.)

Return value:

  • 0 if the thread is joined successfully. Otherwise, it returns an error code.
  • otherwise, it returns an error code.

Additional notes:

  • It is impossible to join a detached thread.
  • When a thread is created, one of its attributes defines whether it is joinable or detached. Detached means it can never be joined. (PTHREAD_CREATE_DETACHED or PTHREAD_CREATE_JOINABLE)

Thread Mutex

Mutex (mutual exclusion) is a synchronization mechanism that allows only one thread to access a resource at a time. It is used to protect shared data from concurrent access. It is used to implement a lock mechanism:

 Thread Mutex
Thread Mutex

Mutex should be declared as follows, where mutex is the name of the mutex.

pthread_mutex_t mutex;

Mutex should be initialized before it is used:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
  • mutex: Pointer to an integer that will store the mutex ID.
  • mutexattr: Mutex attributes. It can be NULL to use the default.

Mutex should be destroyed after it is used:

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • mutex: Pointer to an integer that stores the mutex ID.

To acquire a lock on the specified mutex variable:

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • If the mutex is successfully locked, it returns 0. Otherwise, it returns an error code.
  • If the mutex is already locked by another thread, this will block until the mutex is unlocked.

To attempt to acquire a lock on the specified mutex variable:

int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • If the mutex is successfully locked, it returns 0. Otherwise, it returns an error code.
  • If the mutex is already locked by another thread, it returns error.
  • Useful in preventing deadlocks.

To release a lock on the specified mutex variable:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • If the mutex is successfully unlocked, it returns 0. Otherwise, it returns an error code. For example:
    • The Mutex was already unlocked.
    • The Mutex was owned by another thread.

Related Posts πŸ‘‡

πŸ“‘ Operating System Overview

πŸ“‘ Operating System Security

πŸ“‘ Processes

πŸ“‘ File System

πŸ“‘ Memory Management

Share: Twitter Facebook LinkedIn
Ray's Picture

About Ray

I’m Ray, a computer science student, who loves coding πŸ’», music 🎡 and comics πŸ“š.

Shenzhen, Guangdong, China https://huruilizhen.github.io

Comments