Professional Documents
Culture Documents
Erol Sahin
URL: http://kovan.ceng.metu.edu.tr/ceng334
Week 2 13/03/07 1
13/03/07
1
#include <pthread.h>
Using Pthreads
#include <stdio.h>
int sum; /* this data is shared by the thread(s) */
void *runner(void *param); /* the thread */
void *runner(void *param){ /* The thread will begin control in this function
int i, upper = atoi(param);
sum = 0;
if (upper > 0) {
for (i = 1; i <= upper; i++)
sum += i;
}
pthread_exit(0);
}
Synchronization
Threads cooperate in multithreaded programs in several ways:
2
Shared Resources
We’ll focus on coordinating access to shared resources
Basic problem:
● Two concurrent threads are accessing a shared variable
● If the variable is read/modified/written by both threads, then access to the variable must
be controlled
● Otherwise, unexpected results may occur
Now suppose that you and your friend share a bank account with a
balance of $1500.00
● What happens if you both go to separate ATM machines, and simultaneously withdraw
$100.00 from the account?
3
Example continued
We represent the situation by creating a separate thread for each ATM
user doing a withdrawal
● Both threads run on the same bank server system
Thread 1 Thread 2
int withdraw(account, amount) { int withdraw(account, amount) {
balance = get_balance(account); balance = get_balance(account);
balance -= amount; balance -= amount;
put_balance(account, balance); put_balance(account, balance);
return balance; return balance;
} }
Interleaved Execution
The execution of the two threads can be interleaved
● Assume preemptive scheduling
● Each thread can context switch after each instruction
● We need to worry about the worst-case scenario!
balance = get_balance(account);
balance -= amount;
Execution sequence context switch
as seen by CPU balance = get_balance(account);
balance -= amount;
put_balance(account, balance);
context switch
put_balance(account, balance);
4
Interleaved Execution
The execution of the two threads can be interleaved
● Assume preemptive scheduling
● Each thread can context switch after each instruction
● We need to worry about the worst-case scenario!
Balance = $1500
balance = get_balance(account);
balance -= amount; Local = $1400
Execution sequence
as seen by CPU balance = get_balance(account);
balance -= amount; Local = $1400
put_balance(account, balance); Balance = $1400
Race Conditions
The problem is that two concurrent threads access a shared resource
without any synchronization
● This is called a race condition
● The result of the concurrent access is non-deterministic
● Result depends on:
● Timing
5
Which resources are shared?
Local variables in a function are not shared
● They exist on the stack, and each thread has its own stack
● You can't safely pass a pointer from a local variable to another thread
● Why?
Uninitialized vars
(BSS segment)
Initialized vars
(data segment)
Code
(text segment)
Mutual Exclusion
We want to use mutual exclusion to synchronize access to shared
resources
● Meaning: When only one thread can access a shared resource at a time.
Critical Section
Thread 1
(modify account balance)
6
Mutual Exclusion
We want to use mutual exclusion to synchronize access to shared
resources
● Meaning: When only one thread can access a shared resource at a time.
Critical Section
Thread 2 Thread 1
(modify account balance)
2nd thread must wait
for critical section to clear
Mutual Exclusion
We want to use mutual exclusion to synchronize access to shared
resources
● Meaning: When only one thread can access a shared resource at a time.
Critical Section
Thread 2 Thread 1
(modify account balance)
7
Critical Section Requirements
Mutual exclusion
● At most one thread is currently executing in the critical section
Progress
● If thread T1 is outside the critical section, then T1 cannot prevent T2
from entering the critical section
Performance
● The overhead of entering and exiting the critical section is small with respect to the work
being done within it
Locks
A lock is a object (in memory) that provides the following two
operations:
– acquire( ): a thread calls this before entering a critical section
● May require waiting to enter the critical section
What can happen if acquire( ) and release( ) calls are not paired?
8
Using Locks
put_balance(account, balance);
Thread 1 completes
release(lock);
What happens when the blue thread tries to acquire the lock?
9
Spinlocks
Very simple way to implement a lock:
struct lock {
int held = 0;
}
void acquire(lock) {
while (lock->held); The caller busy waits
lock->held = 1; for the lock to be released
}
void release(lock) {
lock->held = 0;
}
Implementing Spinlocks
Problem is that the internals of the lock acquire/release have critical
sections too!
● The acquire( ) and release( ) actions must be atomic
● Atomic means that the code cannot be interrupted during execution
● “All or nothing” execution
struct lock {
int held = 0;
}
void acquire(lock) {
while (lock->held); What can happen if there
lock->held = 1; is a context switch here?
}
void release(lock) {
lock->held = 0;
}
10
Implementing Spinlocks
Problem is that the internals of the lock acquire/release have critical
sections too!
● The acquire( ) and release( ) actions must be atomic
● Atomic means that the code cannot be interrupted during execution
● “All or nothing” execution
struct lock {
int held = 0;
}
void acquire(lock) {
while (lock->held); This sequence needs
lock->held = 1; to be atomic
}
void release(lock) {
lock->held = 0;
}
Implementing Spinlocks
Problem is that the internals of the lock acquire/release have critical
sections too!
● The acquire( ) and release( ) actions must be atomic
● Atomic means that the code cannot be interrupted during execution
● “All or nothing” execution
● Compare-and-swap
11
Spinlocks using test-and-set
CPU provides the following as one atomic instruction:
struct lock {
int held = 0;
}
void acquire(lock) {
while(test_and_set(&lock->held));
}
void release(lock) {
lock->held = 0;
}
12
Problems with spinlocks
Horribly wasteful!
● Threads waiting to acquire locks spin on the CPU
● Eats up lots of cycles, slows down progress of other threads
● Note that other threads can still run ... how?
● What happens if you have a lot of threads trying to acquire the lock?
Disabling Interrupts
An alternative to spinlocks:
struct lock {
// Note – no state!
}
void acquire(lock) {
cli(); // disable interrupts
}
void release(lock) {
sti(); // reenable interupts
}
13
Disabling Interrupts
An alternative to spinlocks:
struct lock {
// Note – no state!
}
void acquire(lock) {
cli(); // disable interrupts
}
void release(lock) {
sti(); // reenable interupts
}
???
1) Check lock state
Thread 1
14
Mutexes – Blocking Locks
Really want a thread waiting to enter a critical section to block
● Put the thread to sleep until it can enter the critical section
● Frees up the CPU for other threads to run
Thread 1
2) Set state to locked
???
1) Check lock state
Thread 2 Thread 1
15
Mutexes – Blocking Locks
Really want a thread waiting to enter a critical section to block
● Put the thread to sleep until it can enter the critical section
● Frees up the CPU for other threads to run
Thread 2 Thread 1
2) Add self to wait queue (sleep)
???
1) Check lock state
Thread 3 Thread 1
2) Add self to wait queue (sleep)
Thread 2 Thread 3
16
Mutexes – Blocking Locks
Really want a thread waiting to enter a critical section to block
● Put the thread to sleep until it can enter the critical section
● Frees up the CPU for other threads to run
Thread 1
Thread 2 Thread 3
Thread 3 Thread 1
2) Reset lock state to unlocked
Thread 2 Thread 3
17
Mutexes – Blocking Locks
Really want a thread waiting to enter a critical section to block
● Put the thread to sleep until it can enter the critical section
● Frees up the CPU for other threads to run
Thread 2
Limitations of locks
Locks are great, and simple. What can they not easily accomplish?
What if you have a data structure where it's OK for many threads
to read the data, but only one thread to write the data?
● Bank account example.
● Locks only let one thread access the data structure at a time.
18
Limitations of locks
Locks are great, and simple. What can they not easily accomplish?
What if you have a data structure where it's OK for many threads
to read the data, but only one thread to write the data?
● Bank account example.
● Locks only let one thread access the data structure at a time.
Next Lecture
Higher level synchronization primitives:
How do to fancier stuff than just locks
19
dup() and dup2()
#include <unistd.h>
dup2() - filedes2 (if open) will be closed and then set to refer to
whatever filedes refers to
39
list of open
file descriptors
(in PCB)
40
20
Pipes
● A form of interprocess communication between processes that have
a common ancestor
● Typical use:
● Pipe created by a process
● Process calls fork()
● Pipe used between parent and child
● A pipe provides a one-way flow of data
- example: who | sort| lpr
output of who is input to sort
+
41
Pipes
#include <unistd.h>
#include <stdio.h>
int main(void){
int n; // to keep track of num bytes read
int fd[2]; // to hold fds of both ends of pipe
pid_t pid; // pid of child process
char line[80]; // buffer to hold text read/written
if (pipe(fd) < 0) // create the pipe
perror("pipe error");
42
21
After the pipe() call
0 stdin
1 stdout
2 stderr
4
Filedes 0 1
5
3 4
43
0 stdin 0 stdin
stdout 1 stdout
1
stderr 2 stderr
2
3 3
4 4
5 5
Child
Parent
44
22
Parent writing to child
0 stdin 0 stdin
stdout 1 stdout
1
stderr 2 stderr
2
3 X 3
4 4 X
5 5
Child
Parent
45
0 stdin 0 stdin
stdout 1 stdout
1
stderr 2 stderr
2
3 3 X
4 X 4
5 5
Child
Parent
46
23