OS LAB (Project)
OS LAB (Project)
SYNCHRONIZATION
PROJECT
The word synchronization comes up with the meaning of which two or more things which run
or operates at the same time. Synchronization in OS is the coordination between the threads
and the processes to ensure the execution in the correct order which also ensures that which
critical part have to assign to which process to run the system smoothly and safely without
having the crash and also to avid the data inconsistency and race condition. It works in both
environment’s multithreading and also in the multi process. “In simple words if we talk about
the multiprocessors the process which run parallel in system but also, they have their different
memory and spaces if they crash, they the other processes are alive it does not damage it” on
the other hand “if we talk about the multithreading tasks which are run under the single
process it uses the one process memory if any thread crash it damages the other also”. In
synchronization the system executes the synchronization concurrently to ensure the critical
section of code to run it safely by keeping in view that there will be no race condition and
inconsistency.
Importance of OS synchronization:
When the resources are share it maintains the data integrity.
It ensures that there will be no race condition
It supports and cooperate with inter process communication
Examples:
In multithread web server thousands of client requests with handled simultaneously it
ensures the synchronization that data will be updated in cache consistently without
corruption.
In banking system, it ensures the correct transaction details because at the same time
one or even multi transaction will do at the same time
In real time synchronization occurs when multiple players play the game online at the
same time
First thing we know the concept of processes and threads in which the synchronization occurs
Processes:
A process is an independent task which has its own memory space whenever the process loads
into the memory the CPU assigns to the complete process.
Example:
We run two different applications one is the web browser and the other it the Microsoft word
both are different processes
Threads:
Threads are the smaller units with in the single process within the one memory space but it can
execute independently.
Example:
In web browser we run multiple threads in the form of we download from the web browser and
upload, search and the other stuff at the same time.
Shared resources:
The resources in which the processes and the threads want concurrently. It includes when any
process or threads wants to use the i/o devices, memory files etc.
Critical section:
It’s a part of the program in which the thread or the process wants to use but they can only be
executed by only one thread or the process at a time to prevent conflicts. The critical section
contains the shared variable that needs to be synchronized one time to maintain the
consistency of the data. When one process or thread using the critical section, it uses the
critical section and when it exits it generated the message so the other process or thread who
waiting for the critical section to use, they use it.
Code in example:
do {
entry section
critical section
exit section
reminder section
} while (true);
Race condition:
Race condition occurs inside the critical section. It occurs in the unpredictable condition when
the sequence or the timing of the threads not occur at the same time, we also say that it does
not update the latest thread.
Race condition may cause the data corruption on the unexpected behavior.
Example:
When two atm processes withdrawing the money at the same time even they were not
synchronized also they are from the same bank account they might to show the incorrect
balance.
Mutual exclusion:
It ensures that only one process or thread use or enters into the critical condition.
Example:
Lock in a file system when two processes writing concurrently on the same file. Basically, when
one thread who got the access first will lock the file and when it uses all its functionalities it
unlocks the file and run its further tasks so I that way one process and one thread can got
access at only one time.
Its for avoiding the race condition and to ensure the data integrity.
Examples:
It includes shared memory, message passing and pipes.
A parent process sends the data to the child process through the shared memory to share the
compute result.
Synchronization primitives:
Low level mechanisms and the programming languages implements synchronization.
Synchronization problem occurs when the multiple threads or the processes wants to access
the shared resource which we called the critical section at the same time concurrently. It will
lead to potential issues as well as the deadlocks occurs and starvation occurs. To prevent these
problems, we have the three classical synchronization problems.
When the buffer is full the producer does not add the data.
When the buffer is empty the consumer does not remove the data.
Example:
A video streaming service one process uploads the video frames as he is producer and the other
displays any particular video that person is the consumer.
Code:
#include <iostream>
#include <thread>
#include <queue>
#include <semaphore> // C++20 semaphore support
#include <chrono>
using namespace std;
int main() {
// Create producer and consumer threads
thread producer1(producer, 1);
thread producer2(producer, 2);
thread consumer1(consumer, 1);
thread consumer2(consumer, 2);
return 0;
}
Output:
Producer 1 produced item 1
Consumer 1 consumed item 1
Producer 2 produced item 2
Consumer 2 consumed item 2
Producer 1 produced item 3
Consumer 1 consumed item 3
Producer 2 produced item 4
Consumer 2 consumed item 4
Example:
A file can be read by multiple users but it can only be edited by one writer.
Code:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
std::mutex readLock;
std::mutex writeLock;
int readCount = 0;
std::lock_guard<std::mutex> lock(readLock);
readCount++;
if (readCount == 1) {
// Reading section
std::lock_guard<std::mutex> lock(readLock);
readCount--;
if (readCount == 0) {
writeLock.lock();
// Writing section
std::this_thread::sleep_for(std::chrono::milliseconds(500));
writeLock.unlock();
int main() {
std::vector<std::thread> threads;
t.join();
return 0;
Output:
Reader 1 is reading.
Reader 2 is reading.
Reader 0 is reading.
Writer 1 is writing.
Writer 0 is writing.
Writer 2 is writing.
How it works:
process P[i]
while true do
{ THINK;
PICKUP(CHOPSTICK[i], CHOPSTICK[i+1 mod 5]);
EAT;
PUTDOWN(CHOPSTICK[i], CHOPSTICK[i+1 mod 5])
}
if successful = false
then
block(Pi);
{eat}
put down both forks;
Synchronization Mechanisms
Locks:
Locks is the synchronization tool in which only one process access the shared resource and lock
that particular shared resource and then use its functionalities.
Mutex:
Mutex is also a type of lock in which it gathers the shared resource or the critical section and
then the other processes and the threads wait until it does not release it.
Example:
In back we have the multiple users but the mutex lock ensure that only one user modifiers the
balance to avoid the inconsistency.
Code:
#include <iostream>
#include <thread>
#include <mutex>
int sharedResource = 0;
std::mutex mtx;
void increment() {
mtx.lock();
sharedResource++;
mtx.unlock();
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
return 0;
Spin locks:
A spin lock is a type of lock which does not put the threads into waiting mode just like mutex it
checks again and again until the lock become available if it is available, it gathers that shared
resource.
Example:
We can take that example in which the system has high contention with the shared variable but
less with critical section.
Code:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic_flag lock = ATOMIC_FLAG_INIT;
std::cout << "Thread " << threadId << " is in the critical section.\n";
int main() {
t1.join();
t2.join();
return 0;
Recursive locks:
A recursive lock is the type of lock in which threads require the shared resource multiple times
without causing the dead locks.
Example:
A recursive lock ensures that there is no block itself either he is modifying a shared resource in
recursive function.
Code:
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
recursiveFunction(count - 1);
rmtx.unlock();
int main() {
t1.join();
return 0;}
Semaphores
Semaphore is tool used in operating systems to help manage the access of different process to
a shared resources like memory or data without causing problems. Semaphores are used in
cases where we strictly need to limit the access of resources to only one process at a time. A
semaphore is basically lock-based mechanism designed for synchronization. Semaphores use a
counter to control the access of a process to several instances of one resource for eg. There can
be many instances of a CPU what it basically does is that it loops through the instances of a
resource and keeps on going until it finds a instances available or free. Semaphores are
designed for complex synchronization problems where there are multiple processes. It helps in
avoiding problems like race conditions by handling when and how process accesses the
resource.
wait(S) {
while (S <= 0)
; // busy wait
S--;
}
2. Signal() : The signal operation increments the value of semaphore
signal(S) {
S++;
When the semaphore value is zero it means that there is no resource currently available(all
resources are in use) so when a process calls the wait() it is blocked until another process does
signal(). So basically, the wait checks whether the semaphore >0 or not, if it is it decrements the
value of semaphore and continues the execution of the process
Similarly, the signal () operation is called by the process when it completes its execution and
leaves the resource free and increments signal ()
The initial value of a semaphore tells that how many total resources are available.
Uses of semaphores
Mutual exclusion: Semaphores makes sure that only one process gets access to a shared
resource.
Resource management: Gives limited access to resources.
Reader-Writer problem: Makes sure that multiple readers can access resources but no
writer unless there is no reader.
Avoiding deadlocks: Prevents deadlocks by controlling resources access.
Producer-Consumer problem
Producer-Consumer problem is a synchronization problem which involves two threads:
Now the main problem in this case is that we need to make sure:
Code:
#include <iostream>
#include <thread>
#include <queue>
#include <semaphore>
#include <chrono>
#include <mutex>
int main() {
thread producer1(producer, 1);
thread producer2(producer, 2);
thread consumer1(consumer, 1);
thread consumer2(consumer, 2);
producer1.join();
producer2.join();
consumer1.join();
consumer2.join();
return 0;}
Monitors
Monitors are high level abstraction used in synchronization it is a safe way to manage shared
resources by encapsulating them in code which ensures mutual exclusion
Condition variables
A condition variable is a variable used to make the process wait for a certain condition before
proceeding it further implementation it has two mutex :
1. Wait
2. Signal
Use cases
Producer-Consumer problem
The notfull() and notempty() are condition variables in this case
Code:
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
class Monitor
{
private:
std::queue<int> buffer;
const int maxSize;
std::mutex mtx;
std::condition_variable notFull, notEmpty;
public:
Monitor(int size) : maxSize(size) {}
buffer.push(item);
std::cout << "Producer " << id << " produced item " << item << "\n";
int main()
{
Monitor monitor(5); // Shared monitor with buffer size 5
return 0;
}
When multiple process needs to access shared resources, synchronization becomes crucial so
the hardware support helps provide mechanisms that make sure mutual exclusion and
communication
Test-and-set mechanism
In this operation it checks the value of memory location and if it is 1 then it allows the process
to occupy it and sets the value of the memory location to 1 showing that it has been locked and
once the process completes execution and leaves the memory location it returns back to 1
Code:
bool lock = false; // Initial state: lock is available
void acquire_lock() {
while(test_and_set(lock)) {
// Spin: keep trying to acquire the lock
}
}
void release_lock() {
lock = false; // Release the lock
}
Compare-and-swap mechanism
In this mechanism we compare a value of a memory location with expected value or a previous
value and if it matches, we change the value and in case it doesn’t match don’t change it and
try again
Code:
#include <iostream>
#include <atomic>
using namespace std;
bool compare_and_swap(atomic<int>& location, int expected_value, int new_value) {
int old_value = location.load(); // Get the current value
if (old_value == expected_value) {
location.store(new_value); // Update the value atomically
return true; // Successful swap
}
return false; // No swap, values didn't match
}
int main() {
atomic<int> shared_variable(10); // Shared variable initialized to 10
Deadlocks
Prevention strategies
1.Prevention
To prevent going into a deadlock we need to make sure that we invalidate any one of the four
conditions mentioned above
2.Avoidance
It this strategy the system checks that if we grant a resource to a process will the system go into
deadlock
Banker’s Algorithm
This algorithm is the most commonly used algorithm which makes sure that the system
always stays in the safe state and the processes finish without deadlock
WORKING
At first processes declare the maximum number of resources they need then the system
checks that if the resources request is granted will all the process complete with
remaining resources
COMOPONENTS
Process: Represented as circles
Edges:
o Request edge: Directed from process to resource
o Assignment edge: Directed from resource to process
The cycle in the graph tells that there is deadlock
The modern Operating Systems make sure that there is synchronization and concurrency
Overheads of Locking
Different locking mechanism (Mutex, Semaphore) have delays in them because
processes acquire and release shared memory.
This can result in
Increased latency due to lock acquisition and release.
When thread waste time while waiting it effect the CPU efficiency.
1. Java:
a. synchronized keyword
b. CyclicBarrier
c. ReentrantLock
d. CountDownLatch
e. CyclicBarrier
f. Semaphore
2. C++:
a. std::mutex,
b. std::lock_guard
c. std::condition_variable
d. std::unique_lock
e. std::atomic.
Example Code:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std; // Simplifies code by removing the need for std:: prefix
mutex mtx; // Mutex for synchronization
int counter = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
lock_guard<mutex> lock(mtx); // Locks the mutex
counter++; } }
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Final Counter Value: " << counter << endl; // Prints final counter
return 0;}
To overcome this problem this code uses mutex (mtx) to protect the critical section (where
counter is being incremented), inside the function a lock_guard object locks the mutex for
each iteration of the loop which will control the access, updating of counter at a time, the
lock_guard releases the lock automatically when it goes out of the scope to avoid errors and
deadlock.
Emerging Trends in Synchronization
Non-blocking algorithms:
It allows the threads to work on its program or to access the critical section easily without being
blocked by other threads. it increases the performance and scalability.
Example:
In lock free link list, the multiple threads can add or remove nodes without using the locks it
also ensures that there is no bottleneck also ensures the progress.
Lock-free synchronization:
In lock free synchronization one thread can access finite number of steps.
Example:
If one thread is interrupted the other will also push or pop the threads l.
Wait-free synchronization:
Every thread is bounded to complete the number of threads with in the bounded numbers
Example:
Wait free is used in real time in counter system to ensure the thread safety.
Transactional memory:
Works just like the threads by dividing a large transactional into smaller parts or steps then the
transaction rolled back and retired.
Example:
STM allows developers or the group of operations to reduce the complexity and to manage the
locks.
Case Study
Example:
1. Process A (1st User) check the balance (2002) and find that the balance is greater than its
required account (1256)
2. At the same time process B (2nd user) also Checks the balance (2002) and finds it
sufficient for his need (1256)
3. Both make successful transaction at the same time leaving the balance of 746, although
the system should not allow these transactions
This problem is known as Race Condition which result in data inconsistency, which will lead to
financial loss of bank or can lead to severe consequences.
Synchronization Solution:
Proposed Solution:
1. Use Mutex Lock to avoid Multiple threads (Users) to read and write critical sections
2. Make sure that only one thread at a time can access the Critical Section (Balance)
Improved Process:
Process A (1st user) get the access to the Critical Section (Blanca) to read and write in it
and then complete it process
After competition of Process A (1st User), Process B (2nd User) get the access of Critical
Section to do it task.
Challenges In Synchronization
Coherence
Trying to keep data consistency among different caches can be tricky which can
lead to slowing down the overall process
Clock Synchronization
Maintain same clock among different computers Is difficult to achieve due to
network issues or lags
Fault Tolerance
Disconnecting from the network makes it hard for systems to maintain
synchronization
Deadlocks
Hard to maintain synchronization because different parts of the system are stuck
waiting for each other to complete
Conclusion
In modern operating system the data integrity and reliability are important for the essential
synchronization. Without proper synchronization the system will crash it leads to data
corruption and also gives the unpredictable results without using the proper synchronization.
Tools like mutex (it is basically the flag which indicates the lock system into the any thread or
the process as no one will access it until it releases it) and semaphores (it checks the number of
resources that how many resources are available inside the system) are the advanced tools of
synchronization. The transactional memory and the conditional variables reboot the
synchronization challenges. By addressing the issues like deadlocks, race conditions and other
that ensures the safe parallel execution of all the multithreads and multiprocessor
environments. The case study will highlight how the synchronization works in the real-world
applications these principles can solve the real- world problems. It also ensures the seamless
results in critical applications like video streaming. As technology changes now the lock free
algorithms and wait-free algorithms are now used for the efficient synchronization it will
continue a vital role in programming as well as the operating system.
References
CHAT GPT 4 PRO, GEEKS FOR GEEKS, W3SCHOOLS, OS-BOOK.COM, YOUTUBE, COPILOT, WIKIPEDIA