2. The server receives the request, interprets it, and manipulates its resources in
12.4 Shared Variables in Threaded Programs
12.5.4 Using Semaphores to Sch~dule Shared Resources
Another important use of semaphores, besides providing mutual exclusion, is to schedule accesses to shared resources.' In this scenario, a thread uses a semaphore
Producer ~'
thread >---->'. I Bounded buffer I 1 - . _ _ . . ,
- - - - - -
Section 12.5 Synchronizing Threads with Semaphores 1005
Consumer thread
Figure 12.23 Prod~c~r-consunier problem. Tlie producer generates itemk and inserts them into a bounded buffer. The consumer removes itemãs from 'the buffer and thenã
consumes them.
operation to notify another thread that some condition in the program stifte h~s
become true. 1\vo classical and useful examples are the producer-consumer and redders:w'iiters problems. -
J ...
Producer-Consumer Problem
'The producer-consumer'p'roblem is shoWfi in Figure 12.23:'A produ~et and'con- sumer thread'share a bounded buffer with n slots. The producer thread iepeate'dly produces hew items and inserts them in the buffet!'The 'consumer thre'all'iepeat' edly removes items from the buffer and'tlien consumes (uses) iliem. Varia'nt~With ni'ultiple ptoducers and consumers are also possible. " ,
ãã Since inserting and removing items involves updating shared variables, we must' guarantee mutually exclusive 'access to the &'uffer. But guaranteeing mut\.\ai exclusion is not'shffici6nt. We also need' to schedule 'accessesã to the ãbuffer. If the buffer is full (there are no empty slots J: fhen 'the pt8ducei must Wait until a slot becomes available. Similarly, ifth~ buffyrikempty (there are no available items)", then the consumer must wait until an item becomes available.
Producer-consumer interactions occur frequently in r~al ~stems. for exam' pie, in a multimedia system, the producer mi&ht. encpde viqeo frames '_V,hi)e ,the consumer decodes.and renders them on the sqeen. The purpose of the buffer is to redu,ce jitter in the video sti;eam .ca/ifed by daia-dependen~ dj,fferences in th~
encoding and decoding times for individual frames. The buffer provides a reser- voir of slots to the producer and a reservoir of encoded flames to the consumer.
Another common example is the design of graphical user i11terfaces. The prqducer detects mouse and keyboard events and inserts them in the buffer. The consumer removes the events from the buffer in some priority-based manà.er ahtl P?ints the screen.
In this section, we will develop a simple package, called SBuF, for buildi!)g producer-consumer programs. In the next section, we look ai how to use it 1'o build an interesting concurrent server.based on prethreading. SBUF mahipulates bounded buffers of type sbuf _ t (Figure 12.24). Items are stored in aãdynamically allocated integer array (buf) with" n items. 'lbe front and rear indices keep track of.the first and last items iii. the array. Three sem?phores.synchronize access to ~he buffer. The mutex semaphore provides•mutually exclusive buffer access.
Semaphores slots and items are counting semaphoresãthat count the number of empty slots and available itenis~respectively.
j I
!
,I I
I
I
I I
i I
I I
.ll
.1006 Chapter 12 ãConcurrent Programming
1 typedef struct { 2 int *buf;
3 int n;
4 int front;
5 int rear;
6 sem_t mutex;
7 sem_t slots;
8 sem_t items;
9 } sbuf_t;
"
I•
/•
!•
I•
/•
I•
I•
Buffer array */
Maximum number of slots */
buft(front+1)7.nl i~ first item ã~
buf[rear%n] is last item */
Protects accesses to buf */
Counts available slots */
Counts available items */
Figure 12.24 sbuf_t: Bounded buffer used by the SsuF package.
Figure 12.25 sh9ws the implementation of the SBUF pac)<~ge. The sbuf_ini}
funs;tion al\ocates heap, memory for the buffer, ~e\s,front qnd re11r to indicate a!f ,ei;npty. buffer,, and .'),'\~gns initial values to the,tpree semaphores. This function is caller,! once, bef9re,calls_to any of the.other three f'!n~tions. The sbuf_deinit function frees the buffer storage when the, applicatiop is througi} using it; Tl)e sbuf _insert function waits for an available slot, locks the mutex, adds the item, u1:1locks th,e mute~, anq ,then announces the 'availabiiity of a new ite~. The sbuf _ remove f1wction is symmetric. After ~a/ting for.an availabl~ buff!<r item, it lock~
the mutex, removes the it!';l\1 from the f~ont qf the buffer, unlocksã\l].e !"l!tex, and then signals the availability of a new.slot.
llm(!trewiUiifIDW!2ttJi!lttll:millt<m!MHli1Mifil}4il'/611!tftim¥Bil Ut p denote th'e huniber of protlul:e~s, c the •number of consunibrs, and n the buffer size in units bf items. For each of the following 'scenarios, indicate whether tile mutex semaphore in sbuf_ins9rt and f?buf_remOve is necessary of n6t.
A. p,;=l,c=l,n>l B.' p '= 1, c = 1, n = 1 C. p>l,c>l,n=l ., r ' ,
~
w ~eaders-Writers Problem
The readers-writers problem is a generalization of the mutual exclusion problem.
A -collection of concurrent threads is accessing a shared object such as a data structure in main memory or a database on disk. Some threads only read the object, while others modify it. Threads that modify theão]Jject are called writers.
Threads that only read it are called readers. Writers must have exclusive access to the object, but readers may share the objectãwith an unlimited.number of other readers. In general, there are an unbounded number of concurrent readers and writers.
Section 12.S Synchronizing .Threads with Semaphores 1007
I
~~~~~~i~~~~~~~~~~~~~~~~~~~ã~ã~ã~~~ãLã~~~~~~~codelcondsbufc 1
2 3 4 5 6 7, 8 9 10 11 12
'#inclucJ,e "csapp.h"
#include "sbuf.h"
) •'
I• Create an empty, boundetl, shaiea void ~buf_init(sbuf_t •sp, int n)
FIFO• buffer.'with1.fi '~slots •/..
,f {.(
{
,sp->,buf, = Cal!ocGn, ,siz,eo~(ip.t)); 4
sp->n = n; ,~ ,, •} /• Bp.ff1¥" hold,s max o~ n items •/
sp->front = sp->z;ear = 0 ;, !•,Empty buffer iff ;fro:q.t == rear */
Sem_init(&sp->mutex, 0, 1); /•Binary semaphore for locking•/
Sem_init(&sp->slots, 0, n); /•Initially, buf h~s n empty slots•/
l ( ~ '< • ' • •l O) ,ã' I .. f ' .!• I' ' J I
Se~_init &sp->items, 0, • I~itiall7, buf has zero data items *
13 } t •ã1
14
/* Clean up buffer sp */
void•sbuf_deinit(sbuf_tu*sp)
{
},
I
Free(sp->buf);
15 16 17 18 19 20 21 22, 23 24
25
26' 27 28 29
I J I I,
~* Insert item on;to ;,h;,e rear of ,shafE?~ buffer ,i;ip ' void sbuf insertfsbuf t
1*sp,, int item)
•I.
{ ' - • l•1,ã - t, ',Hr •
{ < I I , j iw rl (_I
}
P(&sp;->s ots);
P(.ksp->mutex);
sp',.>bi.lf [ ( ++sp->rear )% (sp->n)]
V(&:sp->mutex);
V(&:sp->items);
'.
30 '10
,,
item;
;f Wtart fol- available slotã*/
I• Lock -Clie' bu'.ffer *j
/*' Insert the f:f~m */
/* Unlock the bUff er */
/*J!Announce available item */
31 /* Remove.,and return ~e first, item~fro{ll buffer SB!',{
32 int sbuf_remove (sbuf_t •sp)
33 {
34 35 3~
37
3~
39 40 "
int item;
P(&sp->items);
P(&sp->mutex);
it~m = sp->buf[(++sp->front)%(sp->n)];
'v(&sp->mutex);
'V(&sp->slots); t: 11£ return item;
/* Wait for available item */
/* Lock thy buffer */
/* Remove the ited *I /* Unlbck the buff~r' '*}
/* Ann'6Jii.Ce availabl'e sl'.ot */
41 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~code/condsbufc
. Hi ' '5 A " 'f h ' •'ã 1 ' • ,I !'rt b . ~ d b ff Figure 12.2~~ BUF: pacl\age or sync ron1z1ng concurrent access to ounue u ers.
,.
J,
I j I
I
I I 1
I
I I
I
I
1008 Chapter 12 Concurrent Programming
Readers-writers.interactions occur frequently in real systems. For example, in an online airline reservation system, an unlimited number of customers are al- lowed to concurrently inspect the seat assignments, but a customer who is booking a seat must have exclusive access to the.database. As another example, in a multi- threaded caching Web proxy, an unlimited number of threads can fetch existing pages from the shared page cache, but any thread that writes a new pageãto the cache must have exclusive access.
The readers-writers problem has 'several variations, each based on the priori- ties of readers and writers. Theãfirst readers-writers problem, which favors readers, requires that no reader be kept waiting unless a writer has alreadytieen granted permission to use the object. In other words, no reader should wait simply because a writer is waiting. The seco1Jd readers-writers problem, which favors writers, re- quires that once a writer is re'ady to write, it performs its write as soon as possible.
Unlike the first problem, a reader that arrives after a writer must wait, even if the writer is also waiting.
Figure 12.26 shows a solution to the first. read~rs-writers problem. Like the solutions to many synchronization problems, it is subtle and deceptively simple.
The w semaphore controls access to the critical sections that ãaccess the shared object. The mutex semaphore protects access to the shared readcnt variable, which counts the number of readers currently in the critical section. A writer locks the wmutex each time it enters the criticl.l section ana'imlocks it each time ilieaves.
This guarantees that there is at most one writer in the critical section'at'any point in time. On the other hand, only the first reader to enter the critical section locks w, and only the last re>1der to leave the critical section unlocks it 'The w mutex is ignored RY re~ders ~ho enter and leave while other readers ar'io p,resent. This means that as long as a single reader holds thew mutex, an unbounded number of readers can enter the critical section unimpeded.
A correct solution to either of the readers-writers problems can result in starvation, where a thread blocks indefinitely and fails to make progress. For example, in the solution in Figure' 12.26, a writer 'could •wait indefinitely while
a stream of readers arrived. '
M:.rac ice t1 e Ơ1:.~ ~ã ;: . ~~~~~~.'zã3;,:;:;,;.:,4
~""'''J'"~~'f!Wr Of J~o ~!!lii~'Ui~~-~'m
The solution to the first readers-writers problem in Figure 12'.26 gives'priority to readers, but thi;priority is weak in the sense that a writer leaving its critical section might ,r<;.start a w'lfting writer instead of a waiting reader.' D~~cribe a scenario where this weak priority would allow a collection of writers to starve a reader.
12.5.5 Putt[ng It_ Together;,A.Concurrent Server Based on Prethreadin~
We have seen how semaphores can be used to access shared variables and to schedule accesses to shared resources. To help you understand these ideas more clearly, let us apply 'them to a concurrent server based on a technique called prethreading.
Section 12.5 Syachronizing Threads with Semaphores 1009
/* Global variables */
int readcnt; /* Initially = 0 */
sem_~ ~utex~ wi /* Both init~ally 1 •/ ..
void reader(vo~d) { while Cl) { '
P(&mutex/;
readcnt++j if (readcnt
P(&w);
V(&mutex);
1) /• First in •/
} }
/* Critical section *I I* Reading happens */
P(&mutex);
readcnt--;
if (readcnt V(,&w);
.V(&mutex);
O) I• Last out •/
void w"r~~er(void)
{ ,,,, J ãi'
l
}
while (1) {
P(&w);
11}*l'Cri'tical section */
,I* Writing happens */
V(&w);
} 11•1ã •
•; ã.r 1~ 1 h I
!;
"
Fig_uJ~ 1,2.26 ,Solution. to \lw first readJlrs-writers pr,oblei;n. Favors read~rs over
writers. ,, •"
'
.Jn the concurrent server in Figure 12.14, we created a new thread for each hew client. A disadvantage of this approach is that we.incur the nontrivial:cost ofi creatipg a new t'lread for each new client. A server 'based onã pre threading tries to '.ieduce: this. overhead by using the producer-consu!"er model shown.Jn Figure lZ/2'7. The server consists of a main thread and"ll set of.worker,thre:ads.
The main thread repeatedly accepts connection requests from clients and places
,. ãã~
•
•
•
1010 Chapter 12ã Concurrent Programming
ii /'l, Ji~ "'; ?~ .... ..,~
Aside Other sync~ronization mecha.nisms , •. ,,. .,., We have shown you how t6 synchronize threads qsing'seni3phdtes~ mainly beC~u'Se the)r areSim'ple;"clas-"' sical, and have a clean semanti~'lnodet But'yotl sholilct,ki\ow'that otlfef sync!ironizaffon tecllniques exist as welL Fqr example, ,JaVa threads 'lue'sytlchrbnize~ with a'rtlechanis1ircalled.aã;fava ~onitor{ 48), which provides a hiiher-level abstraction of the mutual exclusibri and sclieauling capabilities of serl'la"' phores; in fact, \llonitors 'can be 'implem'ente<;I wltJi semaphore§. As. anothpr exalt/pie, 'the ,Pthreads • interface defines a set of synchronization operations on muterand conditi<lwvariables.-Ptlrr!iads mu- texes are used for mutu~l exclusion.' COnditiofr V¥iables are ~sed for sch&dulfng acceSSes to Shiirid ' resources, su~h as tp.e bonnded~ljt.iffer ilia produCer-con~fu,mer'prbgr'am. )
i; l ,~ ~~ ~;~ã~ =~ >/' ~. ã~~ ~ '"""-""";l;';\t,,,,< ~ "''""'~~...!ã... },.._~,_.1-:,.,.,,.,.
Pool of worker threads
~:---~:'.~'.~~-~!'.~~:---ã,--,--- ;;,~~~",;
~-"-.-... Insert
Accept Master descriptors Remove
Buffer
S eo::~-~:~:~_..• thread descrip:ors Worker
---ã thread Service client
Figure 12.27 Organization of a prethreaded concurrent server. A set of eJ$isting threads repeatedly remove and process connected descriptors from a bound'ed buffer.
the resulting connected descriptors in a bounded buffer. Each worker thread repeatedly removes a descriptor from the buffer, services the client, and then waits for the next descriptor.
Figure 12.28 shows how we would use the SBUF package to implement a prethreaded concurrent echo server. After initializing buffer sbuf (line 24), the main thread creates the set of worker threads (lines 25-26). Then it enters the infinite server loop, accepting connection requests and inserting the resulting connected descriptors in sbuf. Each workerã thread has a very simple behavior.
It waits until it is able to' remove a connec!ed descriptor'from the'1mffer (line 39) and tben calls the echo_cnt function to echo client input. ' h
The echo_cnt function in Figure 12.29 is a version of the echo function from Figure 11.22 that records the cumulative number of bytes received from all clients in a global variable called byte_cnt. This is interesting code 'to study because it shows you a general technique for initializing packages that are called from thread routines. In our case, we need to initialize the byte_cnt counter and the mutex semaphore. One approach, which we used for the SBuFland Rm packages, is to require the main thread to explicitly call an initialization'function.
An6'ther approach, shown here, uses the pthread_once function (line 19) to-call
- ã ã
Section 12.5 Synchr.onizing Threads with Semaphores 1Ol1
,..---~---,---code/condechoservert-pre.c 2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
26
27 28
#include "csapp. h"
#include 11sbuf.h11
#define NTHREADS 4
#defin~ SBUFSIZE 16 void echo_cnt(int connfd);
void •thread(void •vargp);
sbuf_t sbuf; /• Shared buffer of connected descriptors•/
in\ main(int argc, char **argv) {
int i, listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
j:ithread_t tid;
if (argc != 2) {
fprintf Cs1:derr, "usage: %s <port>\n", argv[O]);
exit(O);
}
listenfd = Open_listenfd(argv[1]);
sbuf_init(~sbuf, SBUFSIZE);
for (i = O; i < NTHREADS; iã++) Pthread_create C&tid, jNULL, while (1) {
/* Create worker threads */
thread, NULL) ;
29 clientlen ~ sizeof(struct sockaddr_storage);
' J~ I
30 connfd = Accept(listenfd, (SA*) &clientaddr, &clientlen);
31 sbuf_insert(&sbuf, connfd); /* Insert connfd in buffer *I.
32 }
33 } 34
35 ãvdid *thread(void •Varg'P)
36 {
37 Pthread_detach(pthread_self());
38 while (1) {
39 40 41 42 43 }
}
int connfd = sbuf_remove(&sbuf);
echo_cnt(cOnnfd); Ht.1 _.
Close (connfd)ã;
/* Remove connfd from buffer */
/~ Service client •/
r.
----~---""..i---.--'-~-~-~---~"--.----~~~-~ code!condechoservert-pre.c 'f:iJJure 12.28 A ' preth.n:;ad,e~ s:oncurrent ~cho server. The server usJ~s a producer-consumer model with one producer and multiple consumers.
11 1
l
II
l ' •
r I
I
I
11
I I
I
I
1012 Chapter 12 Concurrent Programming
- - ¥ - - - -code/condecho-cnt.c
#include "csapp.h11
2
3 static int byte_cnt; /* Byte counter */
4 static sem_t mutex; /* and the mutex that pr"oiects it *fã
5
6 static void init_echo_cnt(void)
7 {
8 Sem_init(&mutex, 0, 1);
9 byte_cnt = 'o;
10 } 11
12 void echo_cnt(int connfd)
13 {
14 15
int n;
char buf[MAXLINE];
16 rio_t rio;
17 static pthread_on..,ce_t once = PTHREAD_ONCE_INIT;
18
19 Ptàread~once(~onc~, init_echo_cnt);
20 Rio_readinitb(&rio, co~fd):i
21 while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
22 P (&mutex) ; J 1
23 byte_cnt ~= n;
24 printf(11server received %d (%d total) bytes on fd %d\n11,
25 n, byte_cnt, connfd);
26 V (&mutex) ;
27 Rio_writ~n(connfd, buf, n);
28 }'"
29 }
- - - codelcondecho-cnt.c Figure 12.29 echo_cnt: A version of echo that coul)ts all byt~s received from clients.
the initialization function theãfirst time' some thread calls the echo_cnt function.
The advantage of this approach is that it makes the'pllckage easier to use, The disadvantage is that every call to echo_cnt makes a call to pthread_once, which most times does nothing useful.
Once the package is initialized, the echo_cnt function initializes the Rm buffered I/O package (line 20) and then echoes each text line that is received from the client. Notice that the accesses to the shared llytLcnt variable in lines 23-25 are protected by P and V operations. ' ã,
t I
I
I•
I
' '
I ~
' I
I
"
Section 12.6 Using Threads for Parallelism 1013 r:;s~~,~{~~~:ã~~f;;~,P~;--graajm~i~;.-w;ra,ds~~:r,~:'~ ... ,,: ~"1.-~'""""'\;'"i''""'"'----"' _,,, .~ '!? 1
~- ã~ã~ ~ "ãã•1 , .• "' '!f .. " ã ã i , ) > . I
•I/O m~ltiplexiiigcãis,not tlie only way to wrife'lan,.gvelit~tlriven' proglaili. Fd'r•ttxanipte, you might:have I 'rloticl!"d tltiit tfl~ ~bi)currentJpreil\)'e~aect s~i'ver tnay w,e just.aeyelopea iS're"atly an eveni-driv~n ;erver
witl1'siri\ple state.maChiheH&.th~maip'a,fid.work'erJhreactS.'.'IJte' main thi'~a<fhiis iwo.~tates ("waiting '
~-f&~ro:c_onhehct'ion,~re4u~st!i.ahtrr"W3itihg!f0r:avaiia0le btiffefslob-~);~two ilO~evtdts~{'('connectibnããrequest ~ : a'rrives"ããand "btfffef stof:beecihieJ;avhllable•i);'and two tlii,nsitions (''accept-connection request" and. !
"Jnse~t.buJfertt~ni"'f: Similar!'¥,'eiish worl<erlnl'Md 11~.i ~fie'sl~t; fw~iting'for avai'iable buffer item1'): I
~-one t1a ev~.n~~"buff"!ritem )jep~m~'ll,v;:llable'l'):ã ahtl:;o'rle'tf~rl![ti6n.('t~ejri~ve. Buffer iteip"j: !
~-~li~-~-~,,,.,w..~_.J,,.~ ... ~tr~- ,..-.,t~~,,.-M<.~..,.,,~ .. i.,,,"" ~ "'-~ã~J>.. ,,,. ... ~--""~,,,,,,,.,._ """"""""ã -~
, .
Figure 12.30
Relatlonships between the Sets Of sequential, concurrent, and parallel programs.