Скачиваний:
71
Добавлен:
02.05.2014
Размер:
434.18 Кб
Скачать

8.6. "Производители–потребители"

Пусть мы имеем два циклических процесса, которые мы назовем "производитель" и "потребитель". Производитель в каждой итерации своего цикла вырабатывает (производит) порцию информации, которую он помещает в общий для обоих процессов буфер. Предположим для начала, что емкость буфера неограничена. Потребитель в каждой итерации своего цикла выбирает из буфера порцию информации, выработанную производителем, и обрабатывает (потребляет) ее. Потребитель не должен начинать обработку порции, пока ее производство не будет закончено. Задача состоит в синхронизации действий производителя и потребителя таким образом, чтобы не допустить потерь и искажений информации, во-первых, и голодания процессов, во-вторых.

Решение достигается при помощи единственного общего семафора, играющего роль счетчика числа порций в буфере (здесь и далее мы предполагаем структуру семафора, определенную в предыдущем разделе):

1

2

3

4

5

6

7

8

9

10

11

12

12

14

15

16

17

18

19

static semaphore *portCnt =

{ 0, 0, NULL };

static ... buffer ...;

/* процесс-производитель */

void producer ( void ) {

while (1) {

< производство порции >

< добавление порции в буфер >

V(portCnt);

}

}

/* процесс-потребитель */

void consumer ( void ) {

while (1) {

P(portCnt)

< выборка порции из буфера >

< обработка порции >

}

}

Исходное значение семафора portCnt – 0. Производитель каждую итерацию своего цикла заканчивает V-операцией, увеличивающей значение счетчика. Потребитель каждую свою итерацию начинает P-операцией. Если буфер пуст, то потребитель задержится в своей P-операции до появления в буфере очередной порции. Таким образом, если потребитель работает быстрее производителя, он будет время от времени простаивать, если производитель работает быстрее – в буфере будут накапливаться порции.

В реальных задачах такого рода (например, кольцевой буферизации, рассмотренной нами в главе 6) буфер всегда имеет некоторую конечную емкость. Если производитель работает быстрее, то при заполнении буфера он должен приостанавливаться, ожидая освобождения места в буфере. Это легко обеспечить, введя новый семафор freeCnt, выполняющий роль счетчика свободных мест в буфере:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

static semaphore *portCnt =

{ 0, 0, NULL },

*freeCnt = { BSIZE, 0, NULL },

static ... buffer [BSIZE];

/* процесс-производитель */

void producer ( void ) {

while (1) {

< производство порции >

P(freeCnt);

< добавление порции в буфер >

V(portCnt);

}

}

/* процесс-потребитель */

void consumer ( void ) {

while (1) {

P(portCnt)

< выборка порции из буфера >

V(freeCnt);

< обработка порции >

}

}

Попытаемся теперь обобщить решение для произвольного числа производителей и потребителей. Но в таком обобщении мы сталкиваемся с еще одной существенной особенностью. В приведенных выше решениях мы допускали одновременное выполнение операций <добавление порции в буфер> и <выборка порции из буфера>. Очевидно, что при любой организации буфера производитель и потребитель могут одновременно работать только с разными порциями информации. Иначе обстоит дело с многочисленными производителями и потребителями. Два производителя могут попытаться одновременно добавить порцию в буфер и выбрать для этого одно и то же место в буфере. Аналогично два потребителя могут попытаться выбрать из буфера одну и ту же порцию. В следующем примере мы для наглядности представляем буфер в виде кольцевой очереди с элементами (порциями) одинакового размера:

1

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

29

30

31

32

33

34

35

36

37

38

39

typedef ... portion; /* порция информации */

static portion buffer [BSIZE];

static int wIndex = 0, rIndex = 0;

static semaphore *portCnt = { 0, 0, NULL },

*freeCnt = { BSIZE, 0, NULL },

*rAccess = { 1, 0, NULL },

*wAccess = { 1, 0, NULL };

/* имеется NP

аналогичных процессов-производителей */

void producer ( void ) {

portion work;

while (1) {

< производство порции в work >

P(wAccess);

P(freeCnt);

/* добавление порции в буфер */

memcpy(buffer+wIndex,&work,

sizeof(portion) );

if ( ++wIndex == BSIZE ) w_index = 0;

V(portCnt);

V(wAccess);

}

}

/* имеется NC

аналогичных процессов-потребителей */

void consumer ( void ) {

portion work;

while (1) {

P(rAccess);

P(portCnt)

/* выборка порции из буфера */

memcpy(&work, buffer+rIndex,

sizeof(portion) );

if ( ++rIndex == BSIZE ) rIndex = 0;

V(freeCnt);

V(rAaccess);

< обработка порции в work>

}

}

Мы оформляем обращения к буферу как критические секции, защищая их семафорами rAccess и wAccess. Поскольку конфликтовать (пытаться работать с одной и той же порцией в буфере) могут только однотипные процессы, мы рассматриваем буфер как два ресурса: ресурс для чтения и ресурс для записи, и каждый такой ресурс защищается своим семафором. Таким образом, запрещается одновременный доступ к буферу двух производителей или двух потребителей, но разрешается одновременный доступ одного производителя и одного потребителя.

Соседние файлы в папке Системное программирование и операционные системы