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

Operating Systems: Synchronization

This document discusses synchronization and critical sections in operating systems. It explains that instructions from different threads are considered unordered by hardware. Critical sections are sequences of instructions that may produce incorrect results if executed simultaneously. Mutual exclusion is used to ensure critical sections are not executed simultaneously. Locks can be used to enforce mutual exclusion and prevent race conditions by allowing only one thread to acquire a lock and enter the critical section at a time. The document provides examples of how locks can be used to correctly synchronize access to shared resources like a bank account balance.

Uploaded by

oscar
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)
27 views

Operating Systems: Synchronization

This document discusses synchronization and critical sections in operating systems. It explains that instructions from different threads are considered unordered by hardware. Critical sections are sequences of instructions that may produce incorrect results if executed simultaneously. Mutual exclusion is used to ensure critical sections are not executed simultaneously. Locks can be used to enforce mutual exclusion and prevent race conditions by allowing only one thread to acquire a lock and enter the critical section at a time. The document provides examples of how locks can be used to correctly synchronize access to shared resources like a bank account balance.

Uploaded by

oscar
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/ 26

Operating Systems




Synchronization


Lecture 5
Michael O’Boyle

1
Temporal relations

User view of parallel threads

• Instructions executed by a single thread are totally ordered


– A<B<C<…

• In absence of synchronization,
– instructions executed by distinct threads must be considered
unordered / simultaneous
– Not X < X’, and not X’ < X

Hardware largely supports this

2
Example
Example: In the beginning...
main()
Y-axis is “time.”
A
Could be one CPU, could
pthread_create()
be multiple CPUs (cores).
foo()
B
A'

•A<B<C
B' • A' < B'
C • A < A'
• C == A'
• C == B'

3
Critical Sections / Mutual Exclusion

• Sequences of instructions that may get incorrect results if


executed simultaneously are called critical sections
• Race condition results depend on timing
• Mutual exclusion means “not simultaneous”
– A < B or B < A
– We don’t care which
• Forcing mutual exclusion between two critical section
executions
– is sufficient to ensure correct execution
– guarantees ordering

4
Critical sections
Critical sections
is the “happens-before” relation

T1 T2 T1 T2 T1 T2

Possibly incorrect Correct Correct

5
When do critical sections arise?

• One common pattern:


– read-modify-write of
– a shared value (variable)
– in code that can be executed by concurrent threads

• Shared variable:
– Globals and heap-allocated variables
– NOT local variables (which are on the stack)

6
Race conditions

• A program has a race condition (data race) if the result of an


executing depends on timing
– i.e., is non-deterministic

• Typical symptoms
– I run it on the same data, and sometimes it prints 0 and sometimes it
prints 4
– I run it on the same data, and sometimes it prints 0 and sometimes it
crashes

7
Example: shared bank account

• Suppose we have to implement a function to withdraw


money from a bank account:

int withdraw(account, amount) {


int balance = get_balance(account); // read
balance -= amount; // modify
put_balance(account, balance); // write
spit out cash;
}

• Now suppose that you and your partner share a bank


account with a balance of £100.00
– what happens if you both go to separate CashPoint machines, and
simultaneously withdraw £10.00 from the account?

8
• Assume the bank’s application is multi-threaded
• A random thread is assigned a transaction when that
transaction is submitted
int withdraw(account, amount) { int withdraw(account, amount) {
int balance = get_balance(account); int balance = get_balance(account);
balance -= amount; balance -= amount;
put_balance(account, balance); put_balance(account, balance);
spit out cash; spit out cash;
} }

9
Interleaved schedules

• The problem is that the execution of the two threads can be


interleaved:
balance = get_balance(account);
balance -= amount;
Execution sequence context switch
balance = get_balance(account);
as seen by CPU
balance -= amount;
put_balance(account, balance);
spit out cash;
context switch
put_balance(account, balance);
spit out cash;

• What’s the account balance after this sequence?


– who’s happy, the bank or you?
• How often is this sequence likely to occur?

10
Other Execution Orders

• Which interleavings are ok? Which are not?

int withdraw(account, amount) { int withdraw(account, amount) {


int balance = get_balance(account); int balance = get_balance(account);
balance -= amount; balance -= amount;
put_balance(account, balance); put_balance(account, balance);
spit out cash; spit out cash;
} }

11
How About Now?

int xfer(from, to, machine) { int xfer(from, to, machine) {


withdraw( from, machine ); withdraw( from, machine );
deposit( to, machine ); deposit( to, machine );
} }

• Moral:
– Interleavings are hard to reason about
• We make lots of mistakes
• Control-flow analysis is hard for tools to get right
– Identifying critical sections and ensuring mutually exclusive access
can make things easier

12
Another example

i++; i++;

13
Correct critical section requirements

• Correct critical sections have the following requirements


– mutual exclusion
• at most one thread is in the critical section
– progress
• if thread T is outside the critical section, then T cannot prevent
thread S from entering the critical section
– bounded waiting (no starvation)
• if thread T is waiting on the critical section, then T will eventually
enter the critical section
– assumes threads eventually leave critical sections
– performance
• the overhead of entering and exiting the critical section is small
with respect to the work being done within it

14
Mechanisms for building critical sections

• Spinlocks
– primitive, minimal semantics; used to build others
• Semaphores (and non-spinning locks)
– basic, easy to get the hang of, somewhat hard to program with
• Monitors
– higher level, requires language support, implicit operations
– easier to program with; Java “synchronized()” as an example
• Messages
– simple model of communication and synchronization based on
(atomic) transfer of data across a channel
– direct application to distributed systems

15
Locks

• A lock is a memory object with two operations:


– acquire(): obtain the right to enter the critical section
– release(): give up the right to be in the critical section
• acquire() prevents progress of the thread until the lock
can be acquired
• Note: terminology varies: acquire/release, lock/unlock

16
Locks: Example
Locks: Example execution

lock()
lock() Two choices:
• Spin
• Block
unlock() • (Spin-then-block)

unlock()

17
Acquire/Release

• Threads pair up calls to acquire() and release()


– between acquire()and release(), the thread holds the lock
– acquire() does not return until the caller “owns” (holds) the lock
• at most one thread can hold a lock at a time
• What happens if the calls aren’t paired
– I acquire, but neglect to release?
• What happens if the two threads acquire different locks
– I think that access to a particular shared data structure is mediated
by lock A, and you think it’s mediated by lock B?
• What is the right granularity of locking?

18
Using locks
acquire(lock)
balance = get_balance(account);
int withdraw(account, amount) {
balance -= amount;
acquire(lock);
balance = get_balance(account); acquire(lock)

section
critical
balance -= amount; put_balance(account, balance);
put_balance(account, balance); release(lock);
release(lock);
balance = get_balance(account);
spit out cash;
balance -= amount;
}
put_balance(account, balance);
release(lock);
spit out cash;

spit out cash;

• What happens when green tries to acquire the lock?

19
Spinlocks

• How do we implement spinlocks? Here’s one attempt:


struct lock_t {
int held = 0;
}
void acquire(lock) { the caller “busy-waits”,
while (lock->held); or spins, for lock to be
lock->held = 1; released ⇒ hence spinlock
}
void release(lock) {
lock->held = 0;
}

• Race condition in acquire

20
Implementing spinlocks

• Problem is that implementation of spinlocks has critical


sections, too!
– the acquire/release must be atomic
• atomic == executes as though it could not be interrupted
• code that executes “all or nothing”
– Compiler can hoist code that is invariant
• Need help from the hardware
– atomic instructions
• test-and-set, compare-and-swap, …

21
Spinlocks: Hardware Test-and-Set

• CPU provides the following as one atomic instruction:


bool test_and_set(bool *flag) {
bool old = *flag;
*flag = True;
return old;
}

• Remember, this is a single atomic instruction …

22
Implementing spinlocks using Test-and-Set

• So, to fix our broken spinlocks:




 struct lock {

 int held = 0;


 }
void acquire(lock) {

 while(test_and_set(&lock->held));


 }
void release(lock) {

 lock->held = 0;
}

– mutual exclusion? (at most one thread in the critical section)


– progress? (T outside cannot prevent S from entering)
– bounded waiting? (waiting T will eventually enter)
– performance? (low overhead (modulo the spinning part …))

23
Reminder of use …
acquire(lock)
balance = get_balance(account);
int withdraw(account, amount) {
balance -= amount;
acquire(lock);
balance = get_balance(account); acquire(lock)

section
critical
balance -= amount; put_balance(account, balance);
put_balance(account, balance); release(lock);
release(lock);
balance = get_balance(account);
spit out cash;
balance -= amount;
}
put_balance(account, balance);
release(lock);
spit out cash;

spit out cash;

• How does a thread blocked on an “acquire” (that is, stuck in


a test-and-set loop) yield the CPU?
– calls yield( ) (spin-then-block)
– there’s an involuntary context switch (e.g., timer interrupt)
24
Problems with spinlocks

• Spinlocks work, but are wasteful!


– if a thread is spinning on a lock, the thread holding the lock cannot
make progress
• You’ll spin for a scheduling quantum
– (pthread_spin_t)

• Only want spinlocks as primitives to build higher-level


synchronization constructs
– Ok as ensure acquiring only happens for a short time

• We’ll see later how to build blocking locks


– But there is overhead – can be cheaper to spin

25
Summary

• Synchronization introduces temporal ordering


• Synchronization can eliminate races
• Synchronization can be provided by locks, semaphores,
monitors, messages …
• Spinlocks are the lowest-level mechanism
– primitive in terms of semantics – error-prone
– implemented by spin-waiting (crude) or by disabling interrupts (also
crude, and can only be done in the kernel)

26

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy