1. Trang chủ
  2. » Công Nghệ Thông Tin

Thread Synchronization

25 478 1
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Thread synchronization
Trường học Standard University
Chuyên ngành Computer Science
Thể loại Thesis
Năm xuất bản 2023
Thành phố New York
Định dạng
Số trang 25
Dung lượng 81,02 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Thread Synchronization

Trang 1

synchro-of producer-consumer, bounded-buffer, and readers-writers.

3.1 The Producer-Consumer Problem

In the last chapter, we saw the simplest form of mutual exclusion: before accessingshared data, each thread acquires ownership of a synchronization object Once the thread hasfinished accessing the data, it relinquishes the ownership so that other threads can acquire thesynchronization object and access the same data Therefore, when accessing shared data,each thread excludes all others from accessing the same data

This simple form of mutual exclusion, however, is not enough for certain classes ofapplications where designated threads are producers and consumers of data The producerthreads write new values, while consumer threads read them An analogy of the producer-

3

Trang 2

54 Chap 3 Thread Synchronization

consumer situation is illustrated in Figure 3-1, where each thread, producer and consumer, isrepresented by a robot arm The producer picks up a box and puts it on a pedestal (sharedbuffer) from which the consumer can pick it up The consumer robot picks up boxes from thepedestal for delivery If we just use the simple synchronization technique of acquiring andrelinquishing a synchronization object, we may get incorrect behavior For example, the pro-ducer may try to put a block on the pedestal when there is already a block there In such acase, the newly produced block will fall off the pedestal

To illustrate this situation in a multithreaded program, we present an implementation of

a producer-consumer situation with only mutual exclusion among threads

16 // got Mutex, begin critical section

17 cout << "Produce: " << i << endl;

18 SharedBuffer = i;

19 ReleaseMutex(hMutex); // end critical section

20 }

21 } 22 23

Trang 3

3.1 The Producer-Consumer Problem 55

34 cout << "Consumed " << SharedBuffer << ": end of data" << endl;

35 ReleaseMutex(hMutex); // end critical section

36 ExitThread(0);

37 }

38

39 // got Mutex, data in buffer, start consuming

40 if (SharedBuffer > 0){ // ignore negative values

41 result = SharedBuffer;

42 cout << "Consumed: " << result << endl;

43 ReleaseMutex(hMutex); // end critical section

This program creates two threads, Producer and Consumer, which exchange data using

a shared variable, named SharedBuffer All access to SharedBuffer must be fromwithin a critical section The program serializes access to the SharedBuffer, guaranteeingthat concurrent accesses by producer and consumer threads will not corrupt the data in it Asample output from a random run of the program is:

Trang 4

56 Chap 3 Thread Synchronization

As we can see, something is wrong: not every value produced by the producer is sumed, and sometimes the same value is consumed many times The intended behavior is thatthe producer and consumer threads alternate in their access to the shared variable We do notwant the producer to overwrite the variable before its value is consumed; nor do we want theconsumer thread to use the same value more than once

con-The behavior occurs because mutual exclusion alone is not sufficient to solve the ducer-consumer problem—we need both mutual exclusion and synchronization among theproducer and consumer threads

pro-In order to achieve synchronization, we need a way for each thread to communicatewith the others When a producer produces a new value for the shared variable, it must informthe consumer threads of this event Similarly, when a consumer has read a data value, it musttrigger an event to notify possible producer threads about the empty buffer Threads receivingsuch event signals can then gain access to the shared variable in order to produce or consumemore data Figure 3-2 shows our producer and consumer robots with added event signals.The next program uses two event objects, named hNotEmptyEvent and hNot-FullEvent, to synchronize the producer and the consumer threads

Shared Space

Trang 5

3.1 The Producer-Consumer Problem 57

32 // got Mutex, buffer is not full, producing data

33 cout << "Produce: " << i << endl;

34 SharedBuffer = i;

35 BufferState = FULL;

36 ReleaseMutex(hMutex); // end critical section

37 PulseEvent(hNotEmptyEvent); // wake up consumer thread

50 if (BufferState == EMPTY) { // nothing to consume

51 ReleaseMutex(hMutex); // release lock to wait

52 // wait until buffer is not empty

53 WaitForSingleObject(hNotEmptyEvent,INFINITE);

54 continue; // return to while loop to contend for Mutex again

55 }

56

57 if (SharedBuffer == 0) { // test for end of data token

58 cout << "Consumed " << SharedBuffer << ": end of data" << endl;

Trang 6

58 Chap 3 Thread Synchronization

66 ReleaseMutex(hMutex); // end critical section

67 PulseEvent(hNotFullEvent); // wake up producer thread

Note that when a producer or consumer thread wakes up after waiting for an eventobject, it must retest the value of BufferState (lines 22 and 50) in order to handle the sit-uation when there is more than one producer or consumer thread, since another thread maychange the value of BufferState by the time a producer or consumer thread successfully

Trang 7

3.1 The Producer-Consumer Problem 59

regains access to the critical section For example, suppose three consumer threads are ened by an hNotEmptyEvent, and each of them immediately tries to get a mutex lock Thethread that gets the mutex lock will find data in the SharedBuffer, consume it, and setBufferState to EMPTY Assuming that no new data is produced when the other two con-sumer threads get their respective mutex locks some time later, they will find that Buffer-StateEMPTY; so they must wait on the hNotEmptyEvent again

awak-It is important that the producer and consumer threads each wait for the event objectsoutside their critical section; otherwise, the program can deadlock where each thread is wait-ing for the other to make progress For example, suppose we do not release the mutex beforewaiting for the event object:

3.1.1 A Producer-Consumer Example—File Copy

To exemplify producers and consumers, we present a simple multithreaded file copyprogram (see Figure 3-3) The program spawns producer and consumer threads to performthe file copy; the threads share a common data buffer of SIZE bytes The producer threadreads data from the original file up to SIZE bytes at a time into the shared buffer

The consumer thread gets data from the shared buffer, and writes to a new file on disk

1 #include <stdio.h>

2 #include <windows.h>

3

4 #define FULL 1

Trang 8

60 Chap 3 Thread Synchronization

33 count = nbyte; // for use outside of critical section

34 printf(“Produce %d bytes\n”, nbyte);

35 BufferState = FULL;

36 ReleaseMutex(hMutex); // end critical section

37 PulseEvent(hFullEvent); // wake up consumer thread

Trang 9

3.2 The Bounded-Buffer Problem 61

45 WaitForSingleObject(hMutex,INFINITE);

46 if (nbyte == 0) {

47 printf(“End of data, exit consumer thread\n”);

48 ReleaseMutex(hMutex); // end critical section

49 ExitThread(0);

51

52 if (BufferState == EMPTY) { // nothing to consume

53 ReleaseMutex(hMutex); // release lock to wait

54 // wait until buffer is not empty

61 ReleaseMutex(hMutex); // end critical section

62 PulseEvent(hEmptyEvent); // wake up producer thread

3.2 The Bounded-Buffer Problem

The bounded-buffer problem is a natural extension of the producer-consumer problem,where producers and consumers share a set of buffers instead of just one With multiple buff-ers, a producer does not necessarily have to wait for the last value to be consumed beforeproducing another value Similarly, a consumer is not forced to consume a single value each

Trang 10

62 Chap 3 Thread Synchronization

time This generalization enables producer and consumer threads to work much more ciently by not having to wait for each other in lockstep

effi-Extending our analogy of producer and consumer robot arms, in the case of thebounded-buffer problem the shared pedestal is replaced by a conveyer belt that can carrymore than one block at a time (Figure 3-4) The producer adds values at the head of thebuffer, while the consumer consumes values from the tail Each production or consumptionmoves the conveyor belt one step The belt will lock when the buffer is full, and the conveyerbelt stops and waits for a consumer to pick-up a block from it Each time a block is picked up

or a new block is produced, the belt moves forward one step Therefore, consumers read ues in the order in which they were produced

val-A counter, named count, can be used to keep track of the number of buffers in use As

in the producer-consumer situation, we use two events, hNotEmptyEvent and FullEvent, to synchronize the producer and consumer threads Whenever a producer finds

hNot-a full buffer sphNot-ace (count == BUFSIZE), it waits on the event hNotFullEvent larly, when a consumer finds an empty buffer, it waits on the event hNotEmptyEvent.Whenever a producer writes a new value, it signals the event hNotEmptyEvent to awakenany waiting consumers Likewise, when a consumer reads a value, it signals the event hNot-FullEvent to wake any waiting producers The following code illustrates this synchroniza-tion:

Trang 11

3.2 The Bounded-Buffer Problem 63

30 // got Mutex, buffer is not full, producing data

31 cout << "Produce: " << i << endl;

32 SharedBuffer[tail] = i;

33 tail = (tail+1) % BUFSIZE;

34 count++;

35 ReleaseMutex(hMutex); // end critical section

36 PulseEvent(hNotEmptyEvent); // wake up consumer thread

46 if (count == 0) { // nothing to consume

47 ReleaseMutex(hMutex); // release lock to wait

48 // wait until buffer is not empty

49 WaitForSingleObject(hNotEmptyEvent,INFINITE);

51 else if (SharedBuffer[head] == 0) {// test for end of data token

52 cout << "Consumed 0: end of data" << endl;

53 ReleaseMutex(hMutex); // end critical section

54 ExitThread(0);

56 else { // got Mutex, data in buffer, start consuming

57 result = SharedBuffer[head];

58 cout << "Consumed: " << result << endl;

59 head = (head+1) % BUFSIZE;

61 ReleaseMutex(hMutex); // end critical section

62 PulseEvent(hNotFullEvent); // wake up producer thread

65 }

66

Trang 12

64 Chap 3 Thread Synchronization

In general, this program resembles the producer-consumer example on page 56 The

bounded-buffer program is, however, a more efficient way of sharing data when multiple

val-ues can be produced and consumed at the same time In such cases, the producer-consumer

program will force a thread context switch each time a new value is produced or consumed

In the bounded-buffer program, however, a producer thread can produce multiple values

before it relinquishes processing to the consumer thread Likewise, the consumer thread can

consume multiple values during its time slice, when there is available data, before it is forced

to context-switch

An example of the bounded-buffer problem is a multimedia application that

uncom-presses and plays back video Such an application might have two threads that manipulate a

ring-buffer data structure, shown in Figure 3-5 One thread reads data from a disk,

uncom-presses, and fills each display buffer entry in memory, while another thread reads data from

each buffer entry to display it The two threads revolve around the ring supplying and getting

data

Figure 3-5 A Bounded-Buffer of Five Video Frames.

HeadTail

Trang 13

3.2 The Bounded-Buffer Problem 65

3.2.1 Bounded-Buffer File Copy

With the bounded-buffer technique, we can now reimplement the multithreaded file

copy program of Section 3.1.1 with several buffers instead of one (Figure 3-6)

Shared Buffer Shared Buffer Shared Buffer Shared Buffer

Trang 14

66 Chap 3 Thread Synchronization

31 // wait until buffer is not full

45 ReleaseMutex(hMutex); // end critical section

46 PulseEvent(hNotEmptyEvent); // wake up consumer thread

47 } while(n > 0);

48 TheEnd = TRUE; // not thread safe here!

49 printf(“exit producer thread\n”);

56 if ((count == 0) && (TheEnd == TRUE)) {

57 printf(“End of data, exit consumer thread\n”);

58 ReleaseMutex(hMutex); // end critical section

59 ExitThread(0);

61

62 if (count == 0){ // nothing to consume

63 ReleaseMutex(hMutex); // release lock to wait

64 // wait until buffer is not empty

72 printf(“Consumed: wrote %d bytes\n”,SharedBuffer[head]->nbyte);

73 head = (head+1) % BUFSIZE;

74 count ;

75 ReleaseMutex(hMutex); // end critical section

76 PulseEvent(hNotFullEvent); // wake up producer thread

Trang 15

3.2 The Bounded-Buffer Problem 67

3.2.2 Bounded-Buffer with Finer Locking Granularity

To achieve even more concurrency, it is possible to implement a finer level of locking(Figure 3-7) where each buffer in the ring is locked individually In this case, a producer canwrite data in one buffer while a consumer is reading from another We get more concurrency

at the expense of a little more complexity in managing the buffer locks For this, we need twolevels of mutex locks with one to protect the buffer state (i.e., a counter) In addition, theremust be a mutex lock for each buffer in the ring A producer or consumer thread must lock atthe first level, then at the second level, increase the counter, and release the first-level lock sothat other threads can proceed Each thread produces or consumes data in the buffer it haslocked, releases its lock, and then signals

Trang 16

68 Chap 3 Thread Synchronization

Lock

Trang 17

3.2 The Bounded-Buffer Problem 69

45 //and release first level lock so other threads can access other

46 //parts of ring buffer

47 index = tail;

48 tail = (tail+1) % BUFSIZE;

49 count++;

50 ReleaseMutex(hMutex); // release first level lock

51 // still have lock for this buffer, start producing data

59 TheEnd = TRUE; // not thread safe here!

60 printf(“exit producer thread\n”);

68 if ((count == 0) && (TheEnd == TRUE)) {

69 printf(“End of data, exit consumer thread\n”);

70 ReleaseMutex(hMutex); // end critical section

71 ExitThread(0);

73 if (count == 0){ // nothing to consume

74 ReleaseMutex(hMutex); // release lock to wait

75 // wait until buffer is not empty

81 // got first and second level locks, update buffer state

82 // and release first level lock so other threads can access

83 // other parts of ring buffer

84 index = head;

85 head = (head+1) % BUFSIZE;

86 count ;

87 ReleaseMutex(hMutex); // release first level lock

88 // still have lock for this buffer, start consuming data

Trang 18

70 Chap 3 Thread Synchronization

123 // create mutex lock for buffer ring

130 // create producer and consumer threads

131 hThreadVector[0] = _beginthreadex (NULL, 0,

Trang 19

3.3 The Readers-Writers Problem 71

3.3 The Readers-Writers Problem

When multiple threads share data, sometimes strict mutual exclusion is not necessary.This happens when there are several threads that only read a data value, and once in a while a

thread writes into it Such programming situations are examples of the readers-writers

prob-lem For these, an exclusive single read-write protocol is too restrictive Such applicationscan be made more efficient by allowing multiple simultaneous reads and an exclusive write.For example, consider a server program that maintains information on the latest prices for avariety of stocks Suppose that this server program gets requests from several clients to eitherread or change the price of a stock It is quite likely that the server will get many morerequests to read stock prices than to change them In this case, it is more efficient to let multi-ple readers obtain stock prices concurrently, without requiring mutual exclusion for eachreader When a thread wants to update a stock price, however, we have to make sure that noother thread is reading or writing it

We now describe the multiple-readers/single-writer locking protocol in which there aretwo mutually exclusive phases of operation: the read phase and the write phase During a readphase, all reader threads read the shared data in parallel During a write phase, only onewriter thread updates the shared data

If a read phase is in progress, and we continue to allow newly arrived reader threads tojoin those in progress, it is possible that no writer thread can ever have a chance to run Simi-larly, during a write phase, if newly arrived writer threads succeed other writer threads (one at

a time) in updating the data, it is also possible that no reader thread ever gets a chance to run.This problem is known as starvation

To prevent starvation of either reader or writer threads, the locking protocol alternatesthe read and write phases when necessary For example, suppose there are both reader andwriter threads present, at the start of a read phase all existing reader threads at that time areallowed to proceed to read the shared data concurrently All reader threads that arrive afterthis time, while there are pending writer threads, are forced to wait until the next read phase.When all the current reader threads finish, a write phase begins in which one writer thread isallowed access to the shared data Likewise, to prevent starvation of reader threads, otherwriter threads must wait until another write phase begins And when the writer thread fin-ishes, another read phase begins, which allows all the waiting reader threads at this time toproceed The switching of read and write phases continues as long as there are pending readerand writer threads

Things are simpler when we only have reader threads or writer threads If we only havereader threads, there would be no write phase, and all of them are allowed to read the shareddata in parallel Likewise, if we only have writer threads, there will only be one write phasewhere one writer thread is allowed to follow another in updating the shared data, sequentially

In an active system where there are many active reader and writer threads, one will seethat the read and write phases alternate, allowing one batch of reader threads to proceed, fol-lowed by one writer thread, followed by another batch of (newly arrived) reader threads, fol-lowed by a writer thread, and so on

To further illustrate this multiple-readers/single-writer synchronization protocol,Table 3–1 lists the arrival time of five reader threads and two writer threads Assuming that

Ngày đăng: 12/09/2012, 14:40

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

  • Đang cập nhật ...

TÀI LIỆU LIÊN QUAN