10.11. Несколько буферов

10.11. Несколько буферов

Во многих программах, обрабатывающих какие-либо данные, можно встретить цикл вида

while ((n = read(fdin, buff, BUFFSIZE)) > 0) {

 /* обработка данных */

 write(fdout, buff, n);

}

Например, программы, обрабатывающие текстовые файлы, считывают строку из входного файла, выполняют с ней некоторые действия, а затем записывают строку в выходной файл. Для текстовых файлов вызовы read и write часто заменяются на функции стандартной библиотеки ввода-вывода fgets и fputs.

На рис. 10.11 изображена иллюстрация к такой схеме. Здесь функция reader считывает данные из входного файла, а функция writer записывает данные в выходной файл. Используется один буфер.

Рис. 10.10. Процесс считывает данные в буфер, а потом записывает его содержимое в другой файл

Рис. 10.11. Один процесс, считывающий данные в буфер и записывающий их в файл

На рис. 10.10 приведена временная диаграмма работы такой программы. Числа слева проставлены в условных единицах времени. Предполагается, что операция чтения занимает 5 единиц, записи — 7, а обработка данных между считыванием и записью требует 2 единицы времени.

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

Рис. 10.12. Разделение копирования файла между двумя потоками

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

Рис. 10.13. Копирование файла двумя потоками

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

В этих диаграммах мы игнорируем множество тонкостей. Например, большая часть ядер Unix выявляет операцию последовательного считывания файла и осуществляет асинхронное упреждающее чтение следующего блока данных еще до поступления запроса. Это может ускорить работу процесса, считывающего данные. Мы также игнорируем влияние других процессов на наши считывающий и записывающий потоки, а также влияние алгоритмов разделения времени, реализованных в ядре.

Следующим шагом будет использование двух потоков (или процессов) и двух буферов. Это называется классическим решением с двойной буферизацией; схема его изображена на рис. 10.14.

Рис. 10.14. Копирование файла двумя потоками с двумя буферами

На нашем рисунке считывающий поток помещает данные в первый буфер, а записывающий берет их из второго. После этого потоки меняются местами.

На рис. 10.15 изображена временная диаграмма процесса с двойной буферизацией. Считывающий поток помещает данные в буфер № 1, а затем уведомляет записывающий о том, что буфер готов к обработке. Затем считывающий процесс помещает данные в буфер № 2, а записывающий берет их из буфера № 1.

В любом случае, мы ограничены скоростью выполнения самой медленной операции — операции записи. После выполнения первых двух операций считывания серверу приходится ждать две дополнительные единицы времени, составляющие разницу в скорости выполнения операций чтения и записи. Тем не менее для нашего гипотетического примера полное время работы будет сокращено почти вдвое.

Обратите внимание, что операции записи выполняются так быстро, как только возможно. Они разделены промежутками времени всего лишь в 2 единицы, тогда как в предыдущих примерах между ними проходило 9 единиц времени (рис. 10.10 и 10.13). Это может оказаться выгодным при работе с некоторыми устройствами типа накопителей на магнитной ленте, которые функционируют быстрее, если данные записываются с максимально возможной скоростью (это называется потоковым режимом — streaming mode).

Рис. 10.15. Процесс с двойной буферизацией

Интересно, что задача с двойной буферизацией представляет собой лишь частный случай общей задачи производителей и потребителей.

Изменим нашу программу так, чтобы использовать несколько буферов. Начнем с решения из листинга 10.11, в котором использовались размещаемые в памяти семафоры. Мы получим даже не двойную буферизацию, а работу с произвольным числом буферов (задается NBUFF). В листинге 10.18 даны глобальные переменные и функция main.

Листинг 10.18. Глобальные переменные и функция main

//pxsem/mycat2.c

1  #include "unpipc.h"

2  #define NBUFF 8

3  struct { /* общие данные */

4   struct {

5    char data[BUFFSIZE]; /* буфер */

6    ssize_t n; /* объем буфера */

7   } buff[NBUFF]; /* количество буферов */

8   sem_t mutex, nempty, nstored; /* семафоры, а не указатели */

9  } shared;

10 int fd; /* входной файл, копируемый в стандартный поток вывода */

11 void *produce(void *), *consume(void *);

12 int

13 main(int argc, char **argv)

14 {

15  pthread_t tid_produce, tid_consume;

16  if (argc != 2)

17   err_quit("usage: mycat2 <pathname>");

18  fd = Open(argv[1], O_RDONLY);

19  /* инициализация трех семафоров */

20  Sem_init(&shared.mutex, 0, 1);

21  Sem_init(&shared.nempty, 0, NBUFF);

22  Sem_init(&shared.nstored, 0, 0);

23  /* один производитель, один потребитель */

24  Set_concurrency(2);

25  Pthread_create(&tid_produce, NULL, produce, NULL); /* reader thread */

26  Pthread_create(&tid_consume, NULL, consume, NULL); /* writer thread */

27  Pthread_join(tid_produce, NULL);

28  Pthread_join(tid_consume, NULL);

29  Sem_destroy(&shared.mutex);

30  Sem_destroy(&shared.nempty);

31  Sem_destroy(&shared.nstored);

32  exit(0);

33 }

Объявление нескольких буферов

2-9 Структура shared содержит массив структур buff, которые состоят из буфера и его счетчика. Мы создаем NBUFF таких буферов.

Открытие входного файла

18 Аргумент командной строки интерпретируется как имя файла, который копируется в стандартный поток вывода.

В листинге 10.19 приведен текст функций produce и consume.

Листинг 10.19. Функции produce и consume

//pxsem/mycat2.c

34 void *

35 produce(void *arg)

36 {

37  int i;

38  for (i = 0;;) {

39   Sem_wait(&shared.nempty); /* Ожидание освобождения места в буфере */

40   Sem_wait(&shared.mutex);

41   /* критическая область */

42   Sem_post(&shared.mutex);

43   shared.buff[i].n = Read(fd, shared.buff[i].data, BUFFSIZE);

44   if (shared.buff[i].n == 0) {

45    Sem_post(&shared.nstored); /* еще один объект */

46    return(NULL);

47   }

48   if (++i >= NBUFF)

49    i = 0; /* кольцевой буфер */

50   Sem_post(&shared.nstored); /* еще один объект */

51  }

52 }

53 void *

54 consume(void *arg)

55 {

56  int i;

57  for (i = 0;;) {

58   Sem_wait(&shared.nstored); /* ожидание появления объекта для обработки */

59   Sem_wait(&shared.mutex);

60   /* критическая область */

61   Sem_post(&shared.mutex);

62   if (shared.buff[i].n == 0)

63    return(NULL);

64   Write(STDOUT_FILENO, shared.buff[i].data, shared.buff[i].n);

65   if (++i >= NBUFF)

66    i=0; /* кольцевой буфер */

67   Sem_post(&shared.nempty); /* освободилось место для объекта */

68  }

69 }

Пустая критическая область

40-42 Критическая область, защищаемая семафором mutex, в данном примере пуста. Если бы буферы данных представляли собой связный список, здесь мы могли бы удалять буфер из списка, не конфликтуя при этом с производителем. Но в нашем примере, где мы просто переходим к следующему буферу с единственным потоком-производителем, защищать нам просто нечего. Тем не менее мы оставляем операции установки и снятия блокировки, подчеркивая, что они могут потребоваться в новых версиях кода.

Считывание данных и увеличение семафора nstored

43-49 Каждый раз, когда производитель получает пустой буфер, он вызывает функцию read. При возвращении из read увеличивается семафор nstored, уведомляя потребителя о том, что буфер готов. При возвращении функцией read значения 0 (конец файла) семафор увеличивается, а производитель завершает работу.

Поток-потребитель

57-68 Поток-потребитель записывает содержимое буферов в стандартный поток вывода. Буфер, содержащий нулевой объем данных, обозначает конец файла. Как и в потоке-производителе, критическая область, защищенная семафором mutex, пуста.

ПРИМЕЧАНИЕ

В разделе 22.3 книги [24] мы разработали пример с несколькими буферами. В этом примере производителем был обработчик сигнала SIGIO, а потребитель представлял собой основной цикл обработки (функцию dg_echo). Разделяемой переменной был счетчик nqueue. Потребитель блокировал сигнал SIGIO на время проверки или изменения счетчика.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

3.2 СТРУКТУРА ОБЛАСТИ БУФЕРОВ (БУФЕРНОГО ПУЛА)

Из книги Архитектура операционной системы UNIX автора Бах Морис Дж

3.2 СТРУКТУРА ОБЛАСТИ БУФЕРОВ (БУФЕРНОГО ПУЛА) Ядро помещает информацию в область буферов, используя алгоритм поиска буферов, к которым наиболее долго не было обращений: после выделения буфера дисковому блоку нельзя использовать этот буфер для другого блока до тех пор,


§ 1.3 Несколько слов о XML

Из книги Создание электронных книг в формате FictionBook 2.1: практическое руководство [Release 1.01 от 28.II.2010 г.] автора Кондратович Михаил Иосифович

§ 1.3 Несколько слов о XML Расширяемый язык разметки — eXtensible Markup Language, был создан для хранения структурированных данных в текстовом формате. Теоретически файлы XML должны легко читаться, как программным обеспечением, так и человеком.С использованием технологии XML можно


Multiple (Несколько)

Из книги AutoCAD 2010 автора Орлов Андрей Александрович

Multiple (Несколько) Программа AutoCAD выполняет полное сканирование экрана каждый раз, когда происходит выделение объекта. Режим Multiple (Несколько) позволяет выделить несколько объектов без задержки, и при нажатии клавиши Enter все точки будут выбраны за одно сканирование


10.10. Несколько производителей, несколько потребителей

Из книги UNIX: взаимодействие процессов автора Стивенс Уильям Ричард

10.10. Несколько производителей, несколько потребителей Следующее изменение, которое мы внесем в нашу пpoгрaммy, будет заключаться в добавлении возможности одновременной работы нескольких потребителей вместе с несколькими производителями. Есть ли смысл в наличии


Подход третий: Несколько product owner’ов - несколько backlog’ов

Из книги Scrum и XP: заметки с передовой автора Книберг Хенрик

Подход третий: Несколько product owner’ов - несколько backlog’ов Похоже на второй вариант, по отдельному product backlog на команду, только ещё и с отдельным product owner’ом на каждую команду. Мы не пробовали так делать, и, скорее всего, пробовать не будем.Если два product backlog’а касаются одного и


3.14.9. Еще несколько образцов

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл

3.14.9. Еще несколько образцов Завершим наш список несколькими выражениями из категории «разное». Как обычно, почти все эти задачи можно решить несколькими способами.Пусть нужно распознать двузначный почтовый код американского штата. Проще всего, конечно, взять выражение


Несколько выходящих документов

Из книги Технология XSLT автора Валиков Алексей Николаевич

Несколько выходящих документов Как известно, преобразование в XSLT 1.0 имеет один основной входящий документ (плюс документы, доступные при помощи функции document) и ровно один выходящий документ. То есть, для того, чтобы сгенерировать на основе одного входящего документа


Несколько полезных замечаний

Из книги OrCAD PSpice. Анализ электрических цепей автора Кеоун Дж.

Несколько полезных замечаний При создании входного файла для схемы, которую вы хотите исследовать, всегда начинайте с полного эскиза схемы. Разметьте узлы, используя для этого маркировку, отличающуюся по цвету от остального текста, например красные или голубые чернила.


Несколько слов о связи

Из книги Программирование КПК и смартфонов на .NET Compact Framework автора Климов Александр П.

Несколько слов о связи Несомненно, маленькие мобильные устройства, будь то смартфон или КПК, идеально подходят на роль коммуникационных устройств. В этой главе были приведены только самые простые примеры использования связи между устройствами. В последнее время


Как объединить несколько книг

Из книги Fiction Book Designer 3.2. Руководство по созданию книг автора

Как объединить несколько книг 1. Откройте два окна Fiction Book Designer два раза (скажем, окно 1 и окно 2).2. Загрузите книгу в окно 1, выберите весь ее текст (Ctrl+A) и скопируйте его в буфер обмена (Ctrl+Ins или Ctrl+C).3. В окне 2 кликните на то место, куда Вы хотите поместить текст книги и нажмите


Несколько примеров

Из книги Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform автора Кёртен Роб

Несколько примеров Рассмотрим теперь несколько примеров применения каждого метода.Режим с управлением по запросу (send-driven) — модель «клиент/сервер»Файловая система, последовательные порты, консоли и звуковые платы — все это примеры применения модели «клиент/сервер».


4.3.5. Несколько замечаний

Из книги Linux глазами хакера автора Флёнов Михаил Евгеньевич

4.3.5. Несколько замечаний Для полного понимания процесса создания учетных записей нам нужно познакомиться еще с файлом /etc/login.defs. В нем хранятся настройки, которые будут использоваться при добавлении пользователей. Содержимое файла можно увидеть в листинге 4.1.Листинг 4.1.


Распараллеливание на несколько процессоров

Из книги Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil автора Ковязин Алексей Николаевич

Распараллеливание на несколько процессоров Многие разработчики представляют себе понятие распараллеливания по разному поэтому надо внести ясность, что имеется в виду под этим термином в случае InterBase 7. Прежде всего уточним, что речь идет о выполнении SQL-запросов,


8.4. Функции fsync() и fdatasync(): очистка дисковых буферов

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

8.4. Функции fsync() и fdatasync(): очистка дисковых буферов В большинстве операционных систем при записи в файл данные не передаются на диск немедленно. Вместо этого операционная система помещает их в резидентный кэш-буфер с целью сокращения числа обращений к диску и повышения


Буферы и заголовки буферов

Из книги Разработка ядра Linux автора Лав Роберт

Буферы и заголовки буферов Когда блок хранится в памяти (скажем, после считывания или в ожидании записи), то он хранится в структуре данных, называемой буфером (buffer). Каждый буфер связан строго с одним блоком. Буфер играет роль объекта, который представляет блок в