- OS - Home
- OS - Needs
- OS - Overview
- OS - History
- OS - Components
- OS - Structure
- OS - Architecture
- OS - Services
- OS - Properties
- Process Management
- Processes in Operating System
- States of a Process
- Process Schedulers
- Process Control Block
- Operations on Processes
- Inter Process Communication (IPC)
- Context Switching
- Multi-threading
- Scheduling Algorithms
- Process Scheduling
- Types of Scheduling
- Scheduling Algorithms Overview
- FCFS Scheduling Algorithm
- SJF Scheduling Algorithm
- Round Robin Scheduling Algorithm
- HRRN Scheduling Algorithm
- Priority Scheduling Algorithm
- Multilevel Queue Scheduling
- Lottery Scheduling Algorithm
- Starvation and Aging
- Turn Around Time & Waiting Time
- Burst Time in SJF Scheduling
- Process Synchronization
- Process Synchronization
- Solutions For Process Synchronization
- Hardware-Based Solution
- Software-Based Solution
- Critical Section Problem
- Critical Section Synchronization
- Mutual Exclusion Synchronization
- Peterson's Algorithm
- Dekker's Algorithm
- Bakery Algorithm
- Semaphores
- Binary Semaphores
- Counting Semaphores
- Mutex
- Turn Variable
- Bounded Buffer Problem
- Reader Writer Locks
- Test and Set Lock
- Monitors
- Sleep and Wake
- Race Condition
- OS Deadlock
- Introduction to Deadlock
- Conditions for Deadlock
- Deadlock Handling
- Deadlock Prevention
- Deadlock Avoidance (Banker's Algorithm)
- Deadlock Detection and Recovery
- Deadlock Ignorance
- Memory Management
- Memory Management
- Contiguous Memory Allocation
- Non-Contiguous Memory Allocation
- First Fit Algorithm
- Next Fit Algorithm
- Best Fit Algorithm
- Worst Fit Algorithm
- Fragmentation
- Virtual Memory
- Segmentation
- Buddy System
- Slab Allocation
- Overlays
- Paging and Page Replacement
- Paging
- Demand Paging
- Page Table
- Page Replacement Algorithms
- Optimal Page Replacement Algorithm
- Belady's Anomaly
- Thrashing
- Storage and File Management
- File Systems
- File Attributes
- Structures of Directory
- Linked Index Allocation
- Indexed Allocation
- Disk Scheduling Algorithms
- FCFS Disk Scheduling
- SSTF Disk Scheduling
- SCAN Disk Scheduling
- LOOK Disk Scheduling
- I/O Systems
- I/O Hardware
- I/O Software
- OS Types
- OS - Types
- OS - Batch Processing
- OS - Multiprocessing
- OS - Hybrid
- OS - Monolithic
- OS - Zephyr
- OS - Nix
- OS - Linux
- OS - Blackberry
- OS - Garuda
- OS - Tails
- OS - Clustered
- OS - Haiku
- OS - AIX
- OS - Solus
- OS - Tizen
- OS - Bharat
- OS - Fire
- OS - Bliss
- OS - VxWorks
- OS - Embedded
- OS - Single User
- Miscellaneous Topics
- OS - Security
- OS Questions Answers
- OS - Questions Answers
- OS Useful Resources
- OS - Quick Guide
- OS - Useful Resources
- OS - Discussion
Operating System Software-Based Solution for Process Synchronization
To solve the process synchronization problem in a multitasking operating system, we can implement software-based solutions or hardware-based solutions. Software based solutions refers to the algorithms and programming techniques that are used inside processes to achieve synchronization without using any special hardware instructions.
In this chapter, we will focus on understanding the software-based solutions that can be implemented to mutual exclusion and synchronization between multiple processes. Here are some popular algorithms that provide software-based solutions for process synchronization −
Peterson's Algorithm
Peterson's Algorithm is a classic solution for achieving mutual exclusion between two processes. The idea here is to use two shared variables −
- flag[i] − indicates whether process i wants to enter its critical section.
- turn − indicates whose turn it is to enter the critical section.
The algorithm works as follows −
- Change flag[i] to true, (means, process i wants to enter its critical section).
- Set turn to j, indicating that it's the other process's turn.
- While flag[j] is true (the other process wants to enter its critical section) and turn equals j (it's the other process's turn), wait.
- Enter the critical section.
- Set flag[i] to false, indicating that process i has finished its critical section.
Example
The following is the implementation of Peterson's Algorithm in C++
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<bool> flag[2] = {false, false};
atomic<int> turn;
void peterson_process(int i) {
int j = 1 - i;
while (true) {
// Indicate that process i wants to enter its critical section
flag[i] = true;
// Set turn to the other process
turn = j;
// Wait until the other process is not interested or it's this process's turn
while (flag[j] && turn == j) {
// Busy wait
}
// Critical Section
cout << "Process " << i << " in critical section" << endl;
// Exit Section
flag[i] = false;
// Remainder Section
cout << "Process " << i << " in remainder section" << endl;
}
}
int main() {
thread t1(peterson_process, 0);
thread t2(peterson_process, 1);
t1.join();
t2.join();
return 0;
}
The output of the above code will be −
Process 0 in critical section Process 0 in remainder section Process 1 in critical section Process 1 in remainder section ...
Dekker's Algorithm
In Dekker's Algorithm, uses two flags and a turn variable to achieve mutual exclusion between two processes. Every process have its own flag to indicate whether it wants to enter its critical section or not. The turn variable is used to indicate whose turn it is to enter the critical section.
The algorithm works as follows −
- Create a turn variable to indicate who may go first.
- To enter: set your flag[i] = true.
- While the other flag[j] is true and turn == j, set flag[i] = false and wait for your turn; when turn != j set flag[i] = true again.
- Enter the critical section.
- After exit: set turn = j and flag[i] = false.
Example
The following is the implementation of Dekker's Algorithm in C++
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
std::atomic<bool> want1{false};
std::atomic<bool> want2{false};
std::atomic<int> favoured{1};
const int ITERATIONS = 10;
void thread1() {
for (int i = 0; i < ITERATIONS; ++i) {
// indicate intent to enter
want1.store(true);
// entry section: wait while thread 2 wants to enter
while (want2.load()) {
if (favoured.load() == 2) {
// yield to thread 2 and wait until favoured changes
want1.store(false);
while (favoured.load() == 2) {
std::this_thread::yield();
}
want1.store(true);
}
}
// critical section
std::cout << "Thread 1 entering critical section (iteration " << i << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Thread 1 leaving critical section (iteration " << i << ")" << std::endl;
// favour the other thread and exit
favoured.store(2);
want1.store(false);
// remainder section
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void thread2() {
for (int i = 0; i < ITERATIONS; ++i) {
// indicate intent to enter
want2.store(true);
// entry section: wait while thread 1 wants to enter
while (want1.load()) {
if (favoured.load() == 1) {
// yield to thread 1 and wait until favoured changes
want2.store(false);
while (favoured.load() == 1) {
std::this_thread::yield();
}
want2.store(true);
}
}
// critical section
std::cout << "Thread 2 entering critical section (iteration " << i << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Thread 2 leaving critical section (iteration " << i << ")" << std::endl;
// favour the other thread and exit
favoured.store(1);
want2.store(false);
// remainder section
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
std::cout << "Both threads completed." << std::endl;
return 0;
}
The output of the above code will be −
Thread 1 entering critical section (iteration 0) Thread 1 leaving critical section (iteration 0) Thread 2 entering critical section (iteration 0) Thread 2 leaving critical section (iteration 0) Thread 1 entering critical section (iteration 1) .... Both threads completed.
Bakery Algorithm
Bakery Algorithm is another solution for achieving mutual exclusion among multiple processes. The algorithm uses the idea of allocating tokens to customers in a bakery, to decide the order in which they will be served.
To implement the Bakery Algorithm for process synchronization, we use two shared arrays −
- choosing[i](boolean) − Stores whether process i is in the process of choosing a number.
- number[i](integer) − holds the number assigned to process i, This is the token to enter the critical section.
The algorithm works as follows −
- Set the choosing[i] to true, indicating that process i is in the process of choosing a ticket number.
- Now, set the number[i] to 1 plus the maximum ticket number among all processes.
- Then, set choosing[i] to false, indicating that process i has finished choosing its ticket number.
- For each process j, wait until choosing[j] is false (i.e., process j has finished choosing its ticket number).
- For each process j, wait until either number[j] is 0 (i.e., process j is not interested in entering its critical section) or (number[j], j) is greater than (number[i], i) (i.e., process j has a higher ticket number or the same ticket number but a higher process ID).
- Enter the critical section.
- Set number[i] to 0, indicating that process i has finished its critical section.
- Remainder section.
Example
The section below implements the Bakery Algorithm in C++.
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <atomic>
using namespace std;
const int NUM_PROCESSES = 5;
atomic<bool> choosing[NUM_PROCESSES];
atomic<int> number[NUM_PROCESSES];
void bakery_process(int i) {
for (int iter = 0; iter < 5; iter++) {
// Choosing a number
choosing[i] = true;
number[i] = 1 + *max_element(number, number + NUM_PROCESSES);
choosing[i] = false;
// Waiting for other processes
for (int j = 0; j < NUM_PROCESSES; j++) {
while (choosing[j]);
while (number[j] != 0 && (number[j] < number[i] || (number[j] == number[i] && j < i)));
}
// Critical Section
cout << "Process " << i << " is in its critical section." << endl;
// Exit Section
number[i] = 0;
// Remainder Section
cout << "Process " << i << " is in its remainder section." << endl;
}
}
int main() {
vector<thread> processes;
for (int i = 0; i < NUM_PROCESSES; i++) {
choosing[i] = false;
number[i] = 0;
}
for (int i = 0; i < NUM_PROCESSES; i++) {
processes.push_back(thread(bakery_process, i));
}
for (auto &process : processes) {
process.join();
}
return 0;
}
The output of the above code will be −
Process 0 is in its critical section. Process 0 is in its remainder section. Process 1 is in its critical section. Process 1 is in its remainder section. ...
Peterson's Algorithm vs Bakery Algorithm vs Dekker's Algorithm
Here is a comparison between Peterson's Algorithm, Bakery Algorithm, and Dekker's Algorithm based on various factors −
| Factor | Peterson's Algorithm | Bakery Algorithm | Dekker's Algorithm |
|---|---|---|---|
| Idea Behind the Algorithm | Uses two flags and a turn variable to achieve mutual exclusion between two processes. | Uses a ticketing system to allocate numbers to processes, similar to customers in a bakery. | Uses two flags and a turn variable. But more complex than Peterson's algorithm. |
| Number of Processes | Two processes only | Multiple processes (N processes) | Two processes only |
| Complexity | Simple and easy to implement | More complex due to ticketing mechanism | Complex due to multiple flags and turn variable |
| Fairness | Fair, as it uses turn variable | Fair, as it uses ticket numbers | Fair, but can lead to starvation in some cases |
| Performance | Efficient for two processes | Less efficient due to overhead of ticketing | Less efficient due to busy waiting and complexity |
| Use Cases | Suitable for two-process synchronization scenarios | Suitable for multi-process synchronization scenarios | Primarily of theoretical interest; rarely used in practice |
Conclusion
There are three popular algorithm to achieve process synchronization. These are also called as software-based solutions for process synchronization. Peterson's Algorithm is classical algorithm that is simple and efficient for two processes. Dekker's Algorithm is the more complex version of Peterson's algorithm. Bakery Algorithm is more general solution that can be used for more than two processes.