Читаем без скачивания Системное программирование в среде Windows - Джонсон Харт
Шрифт:
Интервал:
Закладка:
} MSG_BLOCK;
/* Одиночный блок, подготовленный к заполнению новым сообщением. */
MSG_BLOCK mblock = { 0, 0, 0, 0, 0 };
DWORD WINAPI produce(void*);
DWORD WINAPI consume(void*);
void MessageFill(MSG_BLOCK*);
void MessageDisplay(MSG_BLOCK*);
DWORD _tmain(DWORD argc, LPTSTR argv[]) {
DWORD Status, ThId;
HANDLE produce h, consume_h;
/* Инициализировать критический участок блока сообщения. */
InitializeCriticalSection (&mblock.mguard);
/* Создать два потока. */
produce_h = (HANDLE)_beginthreadex(NULL, 0, produce, NULL, 0, &ThId);
consume_h = (HANDLE)_beginthreadex (NULL, 0, consume, NULL, 0, &ThId);
/* Ожидать завершения потоков производителя и потребителя. */
WaitForSingleObject(consume_h, INFINITE);
WaitForSingleObject(produce_h, INFINITE);
DeleteCriticalSection(&mblock.mguard);
_tprintf(_T("Потоки производителя и потребителя завершили выполнениеn"));
_tprintf(_T("Отправлено: %d, Получено: %d, Известные потери: %dn"), mblock.sequence, mblock.nCons, mblock.nLost);
return 0;
}
DWORD WINAPI produce(void *arg)
/* Поток производителя — создание новых сообщений через случайные */
/* интервалы времени. */
{
srand((DWORD)time(NULL)); /* Создать начальное число для генератора случайных чисел. */
while (!mblock.f_stop) {
/* Случайная задержка. */
Sleep(rand() / 100);
/* Получить и заполнить буфер. */
EnterCriticalSection(&mblock.mguard);
__try {
if (!mblock.f_stop) {
mblock.f_ready = 0;
MessageFill(&mblock);
mblock.f_ready = 1;
mblock.sequence++;
}
} __finally { LeaveCriticalSection (&mblock.mguard); }
}
return 0;
}
DWORD WINAPI consume (void *arg) {
DWORD ShutDown = 0;
CHAR command, extra;
/* Принять ОЧЕРЕДНОЕ сообщение по запросу пользователя. */
while (!ShutDown) { /* Единственный поток, получающий доступ к стандартным устройствам ввода/вывода. */
_tprintf(_T("n**Введите 'с' для приема; 's' для прекращения работы: "));
_tscanf("%c%c", &command, &extra);
if (command == 's') {
EnterCriticalSection(&mblock.mguard);
ShutDown = mblock.f_stop = 1;
LeaveCriticalSection(&mblock.mguard);
} else if (command == 'c') { /* Получить новый буфер для принимаемых сообщений. */
EnterCriticalSection(&mblock.mguard);
__try {
if (mblock.f_ready == 0) _tprintf(_T("Новые сообщения отсутствуют. Повторите попытку.n"));
else {
MessageDisplay(&mblock);
mblock.nCons++;
mblock.nLost = mblock.sequence – mblock.nCons;
mblock.f_ready = 0; /* Новые сообщения отсутствуют. */
}
} __finally { LeaveCriticalSection (&mblock.mguard); }
} else {
tprintf(_T("Такая команда отсутствует. Повторите попытку.n"));
}
}
return 0;
}
void MessageFill(MSG_BLOCK *mblock) {
/* Заполнить буфер сообщения содержимым, включая контрольную сумму и отметку времени. */
DWORD i;
mblock->checksum = 0;
for (i = 0; i < DATA_SIZE; i++) {
mblock->data[i] = rand();
mblock->checksum ^= mblock->data[i];
}
mblock->timestamp = time(NULL);
return;
}
void MessageDisplay(MSG_BLOCK *mblock) {
/* Отобразить буфер сообщения, отметку времени и контрольную сумму. */
DWORD i, tcheck = 0;
for (i = 0; i < DATA_SIZE; i++) tcheck ^= mblock->data[i];
_tprintf(_T("nВремя генерации сообщения № %d: %s"), mblock->sequence, _tctime(&(mblock->timestamp)));
_tprintf(_T("Первая и последняя записи: %х %хn"), mblock->data[0], mblock->data[DATA_SIZE – 1]);
if (tcheck == mblock->checksum) _tprintf(_T("УСПЕШНАЯ ОБРАБОТКА –>Контрольная сумма совпадает.n"));
else tprintf(_T("СБОЙ –>Несовпадение контрольной суммы. Сообщение запорчено.n"));
return;
}
Комментарии к примеру простой системы "производитель/потребитель"
Этот пример иллюстрирует некоторые моменты и соглашения, касающиеся программирования, которые будут важны для нас на протяжении этой и последующих глав.
• Объект CRITICAL_SECTION является частью объекта (блока сообщения), защиту которого он обеспечивает.
• Каждый доступ к сообщению осуществляется на критическом участке кода.
• Типом переменных, доступ к которым осуществляется разными потоками, является volatile.
• Использование обработчиков завершения гарантирует, что объекты CS будут обязательно освобождены. Хотя в данном случае эта методика и не является для нас существенной, она дополнительно гарантирует, что вызов функции LeaveCriticalSection не будет случайно опущен впоследствии при изменении кода программы. Имейте также в виду, что обработчик завершения ограничен использованием средств С, и его не следует использовать совместно с C++.
• Функции MessageFill и MessageDisplay вызываются лишь на критических участках кода и используют для нужд своих вычислений не глобальную, а локальную память. Кстати, обе они будут применяться и в последующих примерах, но их листинги больше приводиться не будут.
• Не существует удобного способа, при помощи которого поток производителя мог бы известить поток потребителя о наличии нового сообщения, и поэтому поток потребителя должен просто ожидать, пока не будет установлен флаг готовности, который используется для индикации появления нового сообщения. Устранить этот недостаток нам помогут объекты событий ядра.
• Одним из инвариантных свойств, которые гарантируются этой программой, является то, что контрольная сумма блока сообщения будет всегда корректной вне критических участков кода. Другим инвариантным свойством является следующее:
0 <= nLost + nCons <= sequence
Об этом важном свойстве далее еще будет идти речь.
• О необходимости прекращения передачи поток производителя узнает лишь после проверки флага, устанавливаемого в блоке сообщения потока потребителя. Поскольку потоки не могут обмениваться между собой никакими сигналами, а вызов функции TerminateThread чреват нежелательными побочными эффектами, эта методика является простейшим способом остановки другого потока. Разумеется, чтобы эта методика была эффективной, работа потоков должна быть скоординированной. В то же время, подобное решение требует, чтобы поток не блокировался, иначе он не сможет тестировать флаг; способы решения проблемы блокированных потоков обсуждаются в главе 10.
Объекты CRITICAL_SECTION предоставляют в наше распоряжение мощный механизм синхронизации, но, тем не менее, они не в состоянии обеспечить всю полноту необходимых функциональных возможностей. О невозможности отправки сигналов одним потоком другому уже говорилось, кроме того, эти объекты не позволяют воспользоваться конечными интервалами ожидания (time-out). Объекты синхронизации ядра Windows позволяют снизить остроту не только этих, но и других ограничений.
Мьютексы
Объект взаимного исключения (mutual exception), или мьютекс (mutex), обеспечивает более универсальную функциональность по сравнению с объектом CRITICAL_SECTION. Поскольку мьютексы могут иметь имена и дескрипторы, их можно использовать также для синхронизации потоков, принадлежащих различным процессам. Так, два процесса, разделяющие общую память посредством отображения файлов, могут использовать мьютексы для синхронизации доступа к разделяемым областям памяти.
Объекты мьютексов аналогичны объектам CS, однако, дополнительно к возможности их совместного использования различными процессами, они допускают конечные периоды ожидания, а мьютексы, покинутые (abandoned) завершающимся процессом, переходят в сигнальное состояние.[29] Поток приобретает права владения мьютексом (или блокирует (block) мьютекс) путем вызова функции ожидания (WaitForSingleObject или WaitForMultipleObjects) по отношению к дескриптору мьютекса и уступает эти права посредством вызова функции ReleaseMutex.
Как всегда, необходимо тщательно следить за тем, чтобы потоки своевременно освобождали ресурсы, в которых они больше не нуждаются. Поток может завладевать одним и тем же ресурсом несколько раз, и при этом не будет блокироваться даже в тех случаях, когда уже владеет данным ресурсом. В конечном счете, поток должен освободить мьютекс столько раз, сколько она его захватывала. Такая возможность рекурсивного захвата ресурсов, существующая и в случае объектов CS, может оказаться полезной для ограничения доступа к рекурсивным функциям, а также в приложениях, реализующих вложенные транзакции (nested transactions).
При работе с мьютексами мы будем пользоваться функциями CreateMutex, ReleaseMutex и OpenMutex.