Semaphores Semaphores E nable simple synchronization An interface of atomic functions supplying mutual exclusion Programmers dont need to bother with synchronization algorithms Cou nting s ID: 789305
Download The PPT/PDF document "Operating Systems , 132 Synchronization,..." is the property of its rightful owner. Permission is granted to download and print the materials on this web site for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.
Slide1
Operating Systems, 132
Synchronization, Part 2
Semaphores
Slide2Semaphores
E
nable simple synchronization
An interface of atomic functions supplying mutual exclusion
Programmers don’t need to bother with synchronization algorithms
Slide3Cou
nting
s
emaphore
3
Semaphore’s
interface doesn’t enforce the implementation of starvation freedom.All operations are ATOMIC! That is, up(s) is more than just s:=s+1There is no way to access the semaphore’s internal value. Any attempt to access it is a mistake.Always remember to initialize the semaphore
up(S) [the `v’ operation]If (there are blocked processes) wake-up one of themElse S++
down(S) [the ‘p’ operation]If (S≤0) the process is blocked. It will resume execution only after it is woken-upElse S--
Init(
i
)
S =
i
Slide4Negative-valued semaphore
4
NOTE:
If S is negative, there are |S| blocked processes
up(S)
[the `v’ operation]
S++
If (there are blocked processes) wake-up one of themdown(S)
S--If (S<0) the process is blocked. It will resume execution only after it is woken-up
Slide5Counting Semaphores (Barz)
5
down
(S)
down(S2);
down(S1);
S.value--;
if (S.value>0) then up(S2); up(S1);up(S): down(S1); S.value++; if (S.value == 1) then
up(S2); up(S1);S.value=init_value binary-semaphore: S1=1, S2=min(1, init_value), Create a counting semaphore, S, by using binary semaphores??
Variables ?
Slide6Question 1
Consider the following code snippets run by two processes, P and Q:
Add the minimal number of semaphores, initialize them and place them in the code snippets, so the result of the calculation will be correct (5
2
=25).
Process P
loopP: if (n==0) goto endP; n=n-1; goto loopP;endP: print(sqr)
Process QloopQ: sqr = sqr + 2*n +1; goto loopQ;Shared memoryn=5;sqr=0;6
Slide7Question 1
Note that we can break 25 in more than one way:
25=11+9+5=[(2*5+1)+(2*4+1)+(2*2+1)]
25=9+7+5+3+1=[(2*4+1)+(2*3+1)+(2*2+1)+(2*1+1)+(0+1)]
Process P
loopP
: if (n==0) goto endP; down(semA); n=n-1;
up(semB); goto loopP;endP: down(semA); print(sqr)Process QloopQ: down(semB); sqr = sqr + 2*n +1; up(semA); goto loopQ;Shared memoryn=5;sqr=0;semA=1;semB=0;7
Slide88
Question 2 (2007
Moed
A)
8
The following constraint graph is a DAG that defines a partial order over code lines. Each vertex is associated with a single line of code in a possible program. A directed edge e(
u,v) is used to represent the precedence constraint: code line u must be completed prior to the beginning of code line v.
For example, code line S1 should be completed before S2, S5 or S8 are executed, while S6 can only be executed after both S2 and S5 were completed.
Slide9Question 2a
The following code is executed by two processes, A and B.
Process A
Process B
S1
S2 S5 S3 S8 S6
S9 S4 S7
Slide1010
Question 2a
Use
two counting semaphores
with properly initialized values so that in every execution of A and B the execution order of code lines will be consistent with the constraint graph defined above.
That is, add
up and down operations within A and B’s code lines so that no precedence constraint is violated.You may assume that in every execution both A and B run their code only once.Note: P
artial scoring will be given to solutions using more than two semaphores.10
Slide1111
Question 2a
Semaphores:
semA
=0,
semB
=0Process A S1 SemB.up S5 SemB.up
S8 S9 SemA.downS7SemB.upProcess BSemB.downS2S3SemB.downS6SemA.up
SemB.downS411
Slide1212
Question 2b
Give a concise but accurate explanation why a single semaphore is insufficient for maintaining consistency of the above constraint graph and the two processes.
12
Slide1313
Question 2b
A single semaphore is insufficient because there are constraints which require that A waits until B completes one of its lines and vice versa. In such a state, having A signaling B and immediately waiting for it on the same semaphore will result in either a deadlock or a breaking of one of its constraints.
13
Slide14Question 3 – Producer Consumer Problem
14
#define N
100
/* Buffer size */
semaphore mutex = 1;
/* access control to critical section */semaphore empty = N; /* counts empty buffer slots */semaphore full = 0; /* full slots */void producer(void) { int item;
while(TRUE) { produce_item(&item); /* generate something... */ down(&empty); /* decrement count of empty */ down(&mutex); /* enter critical section */ enter_item(item); /* insert into buffer */ up(&mutex); /* leave critical section */ up(&full); /* increment count of full slots */
} }
Slide15void
consume
r
(
void
){ int item; while(TRUE){
down(&full); /* decrement count of full */ down(&mutex); /* enter critical section */ remove_item(&item); /* take item from buffer */ up(&mutex);
/* leave critical section */ up(&empty); /* update count of empty */ consume_item(item); /* do something... */ }}Question 3 – Producer Consumer Problem15
Slide16Question 3
1. The red lines of the following code were swapped. How will this affect the algorithm?
void
producer
(
void) { int item; while(TRUE) {
produce_item(&item); /* generate something... */ down(&empty); /* decrement count of empty */ down(&mutex); /* enter critical section */ up(&mutex); /* leave critical section */ enter_item
(item); /* insert into buffer */ up(&full); /* increment count of full slots */ } }16No mutual exclusion!
Slide17Question 3
2. What will happen now?
void
producer
(
void) { int item; while(TRUE) {
produce_item(&item); /* generate something... */ down(&empty); /* decrement count of empty */ down(&mutex); /* enter critical section */ enter_item(item); /* insert into buffer */ up(&full); /* increment count of full slots */ up(&
mutex); /* leave critical section */ } }17No problems…
Slide18Question 3
3. And now?
void
consume
r
(void){ int item; while(TRUE){
down(&mutex); /* enter critical section */ down(&full); /* decrement count of full */ remove_item(&item); /* take item from buffer */ up(&mutex); /* leave critical section */ up(&empty); /* update count of empty */ consume_item(item);
/* do something... */ }}18Deadlock!
Slide19Question 4 (Moed
B 2010)
An
unfair semaphore
is a semaphore which does not guarantee that the wakeup order of processes is similar to their falling asleep order.
It does, however, provide the following simple guarantee: if there are sleeping processes on the semaphore while an
up operation is invoked, then one of these processes will be woken up (not necessarily the first amongst the waiting processes to do a down).
Slide20Question 4
Now you are required to implement a
starvation free mutual exclusion algorithm
for
three processes
using 3
unfair counting semaphores: R, S and T, initialized to 1. You are not allowed to use any other variable but these semaphores. Add your code and complete the entry and exit section of each process.Briefly explain why your code satisfies both mutual exclusion and starvation freedom.
Slide21Question 4
P1’s code:
entry:
Critical_section
()
exit:
P2’s code:
entry:
Critical_section()exit:P3’s code:entry:Critical_section()
exit:
unfair counting semaphores:
R, S and T initialized to 1
Slide22Question 4
P1’s code:
entry:
Critical_section
()
exit:
P2’s code:
entry:
Critical_section()exit:P3’s code:entry:Critical_section()
exit:
unfair counting semaphores:
R, S and T initialized to 1
down (S)
down (R)
down (R)
down (T)
down (S)
down (T)
up(R)
up(S)
up(T)
up(R)
up(T)
up (S)
Slide23Question 4
Mutual exclusion:
Any process wishing to enter its critical section must successfully complete two ‘down’ operations on two distinct semaphores. Since any process competes over one different “successful down” with each of the other processes, only a single process may successfully enter the critical section at any given moment.
Slide24Question 4
Starvation freedom:
We first note that there is no starvation problem when using an unfair semaphore with 2 processes (convince yourselves!).
Since entrance to the critical section requires passing semaphores which are only shared in pairs no starvation problems will occur.
Slide25Question 4, supplement
Will this solution work?
P1’s code:
entry:
Critical_section
()
exit:
P2’s code:
entry:Critical_section()exit:P3’s code:entry:
Critical_section
()
exit:
unfair counting semaphores:
R, S and T initialized to 1
down (S)
down (R)
down (R)
down (T)
down (T)
down (S)
up(R)
up(S)
up(T)
up(R)
up(S)
up (T)
Deadlock!
Slide26Question 5 (Midterm 2009)
בסעיף זה עליכם לממש
event counter
תוך שימוש בסמאפורים בינאריים
וברגיסטרים (משתנים התומכים בקריאות ובכתיבות בלבד). כפי שלמדתם,
event counter E
מכיל ערך שלם ומאותחל ל- 0. הוא תומך בשלוש פעולות אטומיות:פעולת Advance(E) מקדמת את ערכו של E ב-1 מערך v-1 לערך v (כאשר v-1 הוא ערכו של E לפני הפעולה) ומעירה את כל התהליכים אשר ישנו על E בהמתנה לערך
v. פעולת Await(E,v) גורמת לתהליך הקורא לישון עד אשר ערכו של E מגיע ל-v. אם ערכו של E גדול או שווה ל-v בעת הקריאה לפעולת Await, אזי התהליך ממשיך בריצתו. פעולת Read(E) מחזירה את ערכו הנוכחי של E.ממשו event counter תוך שימוש בסמאפורים בינאריים (ניתן להניח כי הם הוגנים). הניחו כי ישנם N תהליכים המשתמשים ב-
E וכי המזהים שלהם הינם 0, 1,…,N-1. כל תהליך יכול להשתמש במשתנה MyID השומר את המזהה שלו.26
Slide27Question 5
int
waitval
[N] (all are initialized to 0)
int
v = 0semaphore wait[N] (all are initialized to 0)semaphore mutex (initialized to 1)
Advance(E)down(mutex);E.v++;for (i = 0; i < N; i++) if (waitval[i] == E.v)
up(wait[i]);up(mutex);Await(E,v)boolean wait = false;down(mutex)if (v > E.v) { wait = true; waitval[MyId] = v;}up(mutex);if
(wait)
down
(wait[
MyId
]);
Read(E)
return
E.v
;
27
Shared variables
Slide28Question 5b
Counting semaphore
mutex
=
1
Counting semaphore b=0
register v=0, register waiting=0procedure down( ) { mutex.down( ) if (v == 1){
v=0 mutex.up( )} else { waiting=waiting+1 mutex.up( ) b.down( ) }}procedure up( ){ mutex.down( ) if (waiting > 0){
waiting=waiting-1 b.up( )} else if (v == 0) v=1 mutex.up( )}למדתם כי אין זה פשוט לממש סמאפור כללי (counting semaphore) מסמאפורים בינאריים. מסתבר כי גם הכוון ההפוך אינו פשוט. להלן קטע קוד המכיל מימוש של סמאפור בינארי מסמאפורים כלליים (counting semaphores) ומרגיסטרים.28
Slide29Question 5b
ניתן להניח כי
הסמאפורים
הכלליים בהם משתמש הקוד שלמעלה הוגנים. עליכם להסביר מדוע קטע הקוד שלמעלה אינו מממש
סמאפור
בינארי בצורה נכונה.
הסבירו במדויק מהי הסמאנטיקה של סמאפור בינארי. בעל טווח הערכים 0, 1בלבדפעולת down: אם ערך הסמאפור הוא 0 אזי חוסם את התהליך, אחרת משנה את ערך הסמאפור ל-0.
פעולת up: אם ישנו תהליך שממתין- מעיר אותו, אחרת משנה את ערך הסמאפור ל-1.29
Slide30Question 5b
תארו במדויק תסריט בו מספר תהליכים מבצעים פעולות על המימוש לעיל ובו המימוש אינו מקיים סמאנטיקה זו.
Process p1 does down and stops prior to
b.down
Process p2 does down and stops prior to
b.down
Process p3 does up and finishes the operation (b == 1)Process p4 does up and finishes the operation (b == 2)Process p5 does down and finishes the operation (b == 1)
Process p6 does down and finishes the operation (b == 0)התסריט אינו אפשרי כאשר משתמשים בסמפור בינארי מכיוון שלא יכול להיות מצב שבו שני תהליכים יעשו DOWN ושניהם יצליחו.30
Slide31POSIX synchronization primitives
POSIX defines the following:
Semaphore (counting)
Mutex
Condition variable
Read/Write locks
Slide32Mutex
Resembles a binary semaphore, but has the notion of ownership. I.e., only the thread which locked the mutex is allowed to unlock it. Supports the following operations:
pthread_mutex_init
(
mutex,attr)pthread_mutex_destroy
(mutex)pthread_mutex_lock (mutex)pthread_mutex_trylock (mutex)pthread_mutex_unlock (mutex)
Slide33pthread_mutex_init
(
mutex,attr
)
The attributes allow you to set or get:
the
type (deadlocking, deadlock-detecting, recursive, etc).the robustness (what happens when you acquire a mutex and the original owner died while possessing it).the process-shared attribute (for sharing a mutex across process boundaries, useful for multi-threading and multi-processing).the protocol (how a thread behaves in terms of priority when a higher-priority thread wants the
mutex).the priority ceiling (the priority at which the critical section will run, a way of preventing priority inversion).
Slide34pthread_mutex_lock
VS.
pthread_mutex_trylock
The
pthread_mutex_trylock() function shall be equivalent to
pthread_mutex_lock(), except that if the mutex object referenced by mutex is currently locked the call shall return immediately.If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions shall return zero; otherwise, an error number shall be returned to indicate the error.The pthread_mutex_trylock
() function shall return zero if a lock on the mutex object referenced by mutex is acquired. Otherwise, an error number is returned to indicate the error.
Slide35Condition Variables
Are used to wait
until a particular condition occurs
. Enable
threads to atomically release a lock and enter the sleeping state
. A mutex must be associated with a condition variable
. Supports the following operations:pthread_cond_init (condition, attr)pthread_cond_destroy (condition)pthread_cond_wait (condition, mutex)pthread_cond_signal (condition)pthread_cond_broadcast (condition)
Slide36Producer-consumer problem using mutexes
and condition variables
#include <
pthread.h
>
#include <
stdio.h>#define BSIZE 4
#define NUMITEMS 30typedef struct { char buf[BSIZE]; int occupied; int
nextin, nextout; pthread_mutex_t mutex; pthread_cond_t more; pthread_cond_t less;} buffer_t;
Slide37buffer_t
buffer
;
void
*
producer(void *);void * consumer(
void *);main( int argc, char *argv[] ) { int i
; buffer.occupied = 0; buffer.nextin = buffer.nextout = 0; pthread_cond_init(&(buffer.more), NULL);
pthread_cond_init
(&(
buffer
.
less
),
NULL
);
pthread_t
tid
[
2
];
pthread_create
(&
tid
[
1
],
NULL
,
consumer
,
NULL
);
pthread_create
(&
tid
[
0
],
NULL
,
producer
,
NULL
);
for
(
i
=
0
;
i
<
NUM_THREADS
;
i
++)
pthread_join
(
tid
[
i
],
NULL
);
printf
(
"main
() reporting that all %d threads have terminated\n"
,
i
);
}
Slide38void
*
producer
(
void
* parm){ char item[NUMITEMS] =
"IT'S A SMALL WORLD, AFTER ALL."; int i; for(i=0;i<NUMITEMS;i++){ /* produce an item, one character from item[]
*/ if (item[i] == '\0') break; /* stop if at end of string. */ pthread_mutex_lock(&(buffer.mutex)); if (buffer.occupied
>=
BSIZE
)
printf
(
"producer waiting.\n"
);
while
(
buffer
.
occupied
>=
BSIZE
)
pthread_cond_wait
(&(
buffer
.
less
),
&(
buffer
.
mutex
)
);
printf
(
"producer executing.\n"
);
buffer
.
buf
[
buffer
.
nextin
++]
=
item
[
i
];
buffer
.
nextin
%=
BSIZE
;
buffer
.
occupied
++;
pthread_cond_signal
(&(
buffer
.
more
));
pthread_mutex_unlock
(&(
buffer
.
mutex
));
}
}
void
*
consumer
(
void
* parm){ char item; int
i; for(i=0;i<NUMITEMS;i++){ pthread_mutex_lock(&(buffer.mutex)
); if (buffer.occupied <= 0) printf("consumer waiting.\n"); while(buffer.occupied <= 0) pthread_cond_wait(&(
buffer
.
more
),
&(
buffer
.
mutex
)
);
printf
(
"consumer executing.\n"
);
item
=
buffer
.
buf
[
buffer
.
nextout
++];
printf
(
"%c\
n"
,
item
);
buffer
.
nextout
%=
BSIZE
;
buffer
.
occupied
--;
pthread_cond_signal
(&(
buffer
.
less
));
pthread_mutex_unlock
(&(
buffer
.
mutex
));
}
}