0% found this document useful (0 votes)
15 views

Lecture 05

The document discusses synchronization mechanisms for shared resources between threads and processes. It defines critical sections as parts of code that access shared resources where only one thread can access at a time. Common synchronization mechanisms include mutex locks, semaphores, and monitors. Mutex locks use a boolean flag to allow only one thread to access the critical section at a time. Semaphores generalize this approach with counting semaphores. Proper synchronization is needed to avoid race conditions and ensure data integrity when threads and processes access shared resources.

Uploaded by

Basmala. DZ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views

Lecture 05

The document discusses synchronization mechanisms for shared resources between threads and processes. It defines critical sections as parts of code that access shared resources where only one thread can access at a time. Common synchronization mechanisms include mutex locks, semaphores, and monitors. Mutex locks use a boolean flag to allow only one thread to access the critical section at a time. Semaphores generalize this approach with counting semaphores. Proper synchronization is needed to avoid race conditions and ensure data integrity when threads and processes access shared resources.

Uploaded by

Basmala. DZ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Lecture 05

the values of shared variables between threads depend on the logic of the
program
we can have a problem of shared data corruption (compromising data
coherency) due to:
- threads executing in fake concurrency, (thread interrupted and cpu assigned
to another one)
- threads executing in parallel (simultaneous execution on separate cpus/cores)
- these threads share some data
if this is allowed the system may reach an incorrect state.

definition
Race condition: when multiple threads are running in real/fake
concurrency and they share at least one common variable AND the
outcome of the execution depends on the order in which the threads
modify the shared variable.

solution → ensuring only one thread at a time can manipulate it among the
concurrent threads

using 1 bool → may work but sometimes fails


using 2 bools → may work but causes deadlock and fails sometimes

Process Synchronization:
definition

multiprocessing paradigm (pattern) that manages/controls execution and acces


to shared resources between multiple processes/threads

important when order of execution matters (there's logic to enforce)


allows transparent process communication
→ data integrity(coherency)
enforced by some mechanisms:
mutex
semaphores
monitors
peterson's solution
message passing

Critical Section

definition

critical section: part of a program code in which it requests to use shared


resources (on which the access is mutually exclusive i.e. only one process at a
time granted write-mode access and others are denied read/write access )

a program using a shared resource (with mutually exclusive access)


is organized into 3-5 parts:

Program()
{
[remainder section]
entry section -> here the process requests access to
critical section from the os to grant it

critical section // when the process is here we


know no other processes are modifying the shared resource

exit section -> here the process informs the os of leaving


the critical section to wake up any waiting processes
[remainder section]
}

all processes are readers-> no CS problem


one process is a reader + the other is a write -> CS problem and the reading part
also counts as CS

Critical Section Problem:

designing a solution/protocol for process/thread cooperation which must satisfy:


1. Mutual Exclusion: one process at most executing its CS at a time with respect to
the same variables. (modifying the shared resource)
2. Progress: a process/thread outside its CS shouldn't block another from entering
their CS
3. Bounded Waiting: no process/thread should wait indefinitely in the queue (or
spin around indefinitely) to access its CS

no guarantee they will work correctly in modern architectures

no assumptions on CPU numbers/speed


a process can be interrupted during its CS
in modern solutions we use a queue of PCBs (go to sleep) instead of spinning
(wasting cpu cycles)

Synchronization mechanisms:

Mutex Locks:

mutual exclusion locks : simplest software-based mechanism


consists of a bool M, can be exclusively accessed/updated using two primitives:
acquire() lock , entry section code = lock(), to access CS → if M=true , it
changes its value to M=false and enters CS. else it actively keeps waiting
for the lock to be available, i.e. M=true
release() lock, exit section code = unlock(), must be done by same
process that acquired the lock

acquire(M){
while(!M);
M=false;
}
release(M){
M=true;
}
Program(){
acquire(M);
critical section;
release(M);
}

mutual exclusive access to CS may violated if context switching occurs when the
processes are acquiring the lock (after while(!M) and before M=false )

the primitives must be atomic = one acquire should be running for a given
Mutex at a time in the entire system.
mutex locks (spinlocks) create busy-waiting (spinning, wastes cpu cycles) →
mutex useful when CS is short (→ short spin time + no context-switching)
spinning processes get interrupted giving the change for the lock holder to
release it.
example on deadlock:
forgetting to release (bad coding)
(uniprocessor systems): if ISR(interrupt handler ) tries to acquire but the
lock isn't available, we cannot interrupt the ISR while it's spinning to
release the lock → system gets stuck

Atomic Implementation

Test & Set instruction: (abstract)

hardware implements CPU instruction that writes to memory location, returns


old value atomically without being interrupted. logical representation:

int test_and_set(int *L)


{
int prev = *L;
*L=0;
return prev;
}
// prev content gets loaded into a register and memory is set to
0. (false)

Program(){
//remainder section
while(test_and_set(&lock)==0);
//critical section
lock=1;
//remainder section
}
if 2 cpus execute test_and_set at the same time, hardware ensures that the 2
function calls happen atomically+sequentially.

Intel XCHG instruction: ,

hardware provides XCHG instruction to write into memory location and return old
val in uninterrupted way. ex: Mov AX,0 then XCHG Ax, [0x0E3045] , Logical
representation:

int XCHG(int *L,int v)


{
int prev = *L;
*L=v;
return prev;
}

Program(){
//remainder section
while(XCHG(&lock, 0)==0);
//critical section
lock=1;
//remainder section
}

atomic + indivisible execution, but doesn't lock access to system bus


in uniprocessor systems, processor cannot context-switch in middle of XCHG,
takes two indivisible cpu cycles. → everything works fine
in multiprocessor/core systems, may fail → we need a mechanism to not let
other CPUs run XCHG at the same time. so we add LOCK to the instruction, (it
becomes LOCK XCHG [address],value ) (locks access to system bus)

Semaphores:

generalized software mechanism for CS problem and process synchro

consists of an int variable S + 2 atomic primitives


P() : (Proberen- test) tests the value of the semaphore, decrements if >= 1,
else wait. (this is also denoted as acquire(), wait(),down() )
V() : (Verhogen - increment) : increments semaphore val. (also denoted as
release(),signal(),up() )
P(S){ // acquire
while (S<=0);// uses busy-waiting i.e spinning
S=S-1;
}
V(S){ // release
S=S+1;
}

binary semaphores: range btwn 0,1


counting semaphores: unrestricted domain in Z
those are semaphore types, we also have two styles of use: waiting style, and
mutual exclusion style.
different implementations: the first one with busy-waiting(spinning) and a
second one without busy waiting (uses waiting queue)
second implementation → process can block itself using block() when
trying to acquire unavailable semaphore, wakes up another process (in the
queue) using wake() when releasing the semaphore.

Waiting style:

M initialized to 0, one thread/process stuck when trying to acquire the lock, other
runs and releases M, then the first would be able to acquire it and run.

Mutual Exclusion style:

M initialized to n >=1, works like mutex locks

counting semaphores can be used to limit how many instances of a section of


code can be executed at once. by initializing M to the limit.

better implementation

to eliminate busy waiting:

1. int S holds number of instances of a resource available for simultaneous use.


2. a semaphore queue Q where blocked processes wait for availability of a
resource (CS)
semaphore can only be modified by
P() claim control of a resource/wait if unavailable
V() release resource + wake up a blocked process/thread in queue if there are
any

P(S){
S=S-1;
if(s<0)
{
block and place P in Q;
}
}
V(S){
S=S+1;
if (S<=0)
{
Wake up P from head of Q;
}
}

Binary Semaphores (enhanced mutex lock)

P nullifies S , places all other processes in Q


V sets S to 1 and release a blocked process from Q

P(S){
if(S==1) s=0;
else {Block and place P in Q;}
}
V(S){
if (Q=empty) S=1;
else wake up P from Q;
// each proces, when resumed will STRAIGHTFORWARDLY start
executing its CS
}

Peterson's algorithm

solution for CS involving 2 processes that alternate their CS execution.

Pi(
do {
Interested[i]=true;
turn = j;
while (Interested[j] && turn==j);
CS
Interested[i]=false;
. . .
} while(true);
)

interested[] = ready to enter CS, turn = who's turn it is to enter CS


this fulfills the requirements for CS protocols but has busy waiting

Hardware solutions for CS

easy but inefficient


some OS on uniprocessor systems disable all interrupts when a shared
variable is being manipulated, then enable them before leaving CS → this
only affect the disabling cpu.
modern solution : instruction that performs indivisible rw operation on
memory location, like the LOCK XCHG one. = real hardware implementation
of abstract functions like test_and_set()

You might also like