Home | Projects | Notes > Multi-Threading (POSIX Threads) > Introduction to Threads
A thread is a basic unit of execution flow (i.e., an independent set of instructions which execute).
A process has at least one thread (i.e., main thread)
The main function of the program runs as a main thread.
A thread runs in the context of a process.
A thread can create other threads, other threads can create more threads and so on.
The point where a new thread is created is called fork point.
A thread which creates a new thread is called parent thread, whereas a thread which is created is called child thread.
Make sure that you always pass the persistent memory as an argument to the thread (e.g., static variable or dynamically allocated variables on the heap). Do not pass the caller's local variables or stack memory whose lifetime is bound to the caller's lifetime because they will go away when the caller terminates.
Race condition on thread creation refers to an indeterministic situation where which thread's instruction is going to be executed first immediately after the fork point.
99.9% of the time time 'Instruction 1' will be executed first due to the scheduling overhead, but it is a good practice as a programmer not to assume which instruction is going to be executed first when writing a multi-threaded program.
Three ways in which threads can be terminated:
Return of the thread function
pthread_exit(0);
Thread cancellation (when a thread is canceled or killed by another thread)
Any thread can cancel any other threads by sending the cancellation request
One thread can cancel multiple threads by sending the cancellation request
If main thread terminates its threads will be automatically terminated by default, but not vice-versa. To prevent this from happening, use pthread_exit();
API in the main thread. This is mostly used in cases where the main thread is only required to spawn threads and leave the threads to do their job.
Termination of the spawned threads will not affect any other threads including the main thread.
Following program will create one thread, which is called the main thread, and that main thread will execute the function main
.
xxxxxxxxxx
811/*
2 * File Name : hello_world.c
3 * Description : C program to demonstrate the use of POSIX threads
4 * Author : Modified by Kyungjae Lee (Original: Abhishek Sagar)
5 * Date Created : 12/25/2022
6 */
7
8 /*
9 * Compile using:
10 * gcc -g -c hello_world.c -o hello_world.o
11 * gcc -g hello_world.o -o hello_world -lpthread
12 * Or simply using:
13 * gcc -g hello_world.c -o hello_world -lpthread
14 *
15 * Run using:
16 * ./hello_world
17 */
18
19
20
21/* POSIX threads */
22/* pause(), sleep() */
23
24/* A thread callback function must have the following prototype:
25 * void* (*thread_fn)(void *) */
26static void* thread_fn_callback(void *arg)
27{
28 char *input = (char*)arg;
29 int a = 0;
30
31 while (a < 10)
32 {
33 printf("input string = %s\n", input);
34 sleep(1);
35 if (a == 5)
36 pthread_exit(0); /* one way of terminating the thread */
37 a++;
38 }
39
40 return input;
41}
42
43void thread1_create()
44{
45 /* opaque data structure (as a programmer don't need to know its internal structure) */
46 pthread_t pthread1; /* a.k.a. thread handle */
47
48 /* Take some argument to be passed to the thread function.
49 * Make sure that you always pass the persistent memory as an argument to the thread.
50 * (e.g., static variable or dynamically allocated variables on the heap)
51 * Do not pass caller's local variables or stack memory because these variables will
52 * go away when the caller terminates. */
53 static char *thread_input1 = "I am thread no 1";
54
55 /* pthread_create() - Inbuilt API provided by POSIX thread library
56 * Returns 0 on success, error code otherwise
57 * 1st arg: address of the thread handle
58 * 2nd arg: NULL
59 * 3rd arg: thread function that the new thread will execute
60 * 4th arg: memory address that contains the data to be bassed to the thread
61 * All pthread functions return negative error code on failure and do not set global
62 * 'errno' variable. */
63 int rc = pthread_create(&pthread1, NULL, thread_fn_callback, (void*)thread_input1);
64
65 if (rc != 0)
66 {
67 printf("Error: Thread could not be created. (errno = %d\n)", rc);
68 exit(0);
69 }
70}
71
72int main(int argc, char *argv[])
73{
74 thread1_create();
75
76 /* 'pthread_exit()' API is used to prevent the spawned threads from being terminated
77 * when the main thread terminates */
78 pthread_exit(0);
79
80 return 0;
81}
Operating system allocates resources to threads - Memory, CPU, access to hardware, etc.
All threads are siblings. There is no parent-child (having extra privileges) relationship between threads of the same process; no hierarchy.
Every thread has its own life-cycle (birth, life and death) independent from other threads in the system. (Exception: If main thread terminates its threads will be automatically terminated by default, but not vice-versa.)
Multiple threads of the same process share the same virtual address space of that process except that they have their own stack memory. Resources (that does not use the stack memory) allocated by one thread is visible to the rest of the threads. (e.g., Heap memory, sockets, file descriptors, global variables, etc.)
Stack memory is private to each thread.
All spawned threads share the process' virtual memory space. (No separate virtual memory space for spawned threads.) But, each stack memory segment is private to the corresponding thread.
Kernel (OS) does not schedule processes, but it schedules threads. Thread is a schedulable entity, not a process. Thread, a basic unit of execution flow, is to be allocated (by the OS) to the CPU for execution.
However, this rule is violated in certain error conditions:
If a thread segmentation-faults, the entire process (including all threads spawned by the process) is terminated.
A signal is delivered (by the OS) to the process level, not to the thread level.
e.g., When the SIGSEGV signal is delivered to a process by the OS, the process is terminated with the segmentation-fault.
The race condition on thread creation is due to the fact that which thread the kernel chooses to allocate CPU; the parent thread or the spawned child thread.
Kernel schedules threads on multiple CPUs as per the scheduling policy (e.g., FCFS, SJF).
Sagar, A. (2022). Part A - Multithreading & Thread Synchronization - Pthreads [Video file]. Retrieved from https://www.udemy.com/course/multithreading_parta/