Синхронизация потоков

Описание способов повышения производительности и реализации программ при помощи потоков. Изучение схемы разделения общей памяти несинхронизированными потоками, критические участи программного кода. Функции синхронизации и взаимоблокировки потоков.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 10.04.2013
Размер файла 1,0 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Один из возможных вариантов правильного решения заключается в том, чтобы защитить циклы ожидания при помощи мьютекса или объекта CRITI-CAL_SECTION, как показано в приведенном ниже фрагменте программного кода:

/* Уменьшаем значение счетчика семафора на 2. */

EnterCriticalSection(&csSem);

WaitForSingleObject(hSem, INFINITE);

WaitForSingleObject(hSem, INFINITE);

LeaveCriticalSection (&csSem);

ReleaseSemaphore(hSem, 2, &PrevCount);

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

Казалось бы, можно воспользоваться функцией WaitForMultipleObjects с использованием одного и того же дескриптора семафора в нескольких элементах массива дескрипторов. Однако такое предложение было бы неудачным по двум причинам. Прежде всего, обнаружив, что два дескриптора указывают на один и тот же объект, функция WaitForMultipleObjects завершится с ошибкой. Более того, даже если значение счетчика семафора будет составлять только 1, сигнализироваться будут все дескрипторы, что противоречит самой исходной цели.

Полное решение проблемы множественных циклов ожидания предлагается в упражнении 10.11.

Проектировать семафоры Windows было бы гораздо удобнее, если бы существовала возможность выполнять множественные циклы ожидания в виде одной атомарной операции (atomic multiple-wait operation).

События

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

Важной дополнительной возможностью, обеспечиваемой объектами событий, является то, что переход в сигнальное состояние единственного объекта события способен вывести из состояния ожидания одновременно несколько потоков. Объекты события делятся на сбрасываемые вручную и автоматически сбрасываемые, и это их свойство устанавливается при вызове функции CreateEvent.

- Сбрасываемые вручную события (manual-reset events) могут сигнализировать одновременно всем потокам, ожидающим наступления этого события, и переводятся в несигнальное состояние программно.

- Автоматически сбрасываемые события (auto-reset event) сбрасываются самостоятельно после освобождения одного из ожидающих потоков, тогда как другие ожидающие потоки продолжают ожидать перехода события в сигнальное состояние.

События используют пять новых функций: CreateEvent, OpenEvent, SetEvent, ResetEvent и CreateEvent.

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpsa, BOOL bManualReset, BOOL bInitialState, LPTCSTR lpEventName)

Чтобы создать событие, сбрасываемое вручную, необходимо установить значение параметра bManualReset равным True. Точно так же, чтобы сделать начальное состояние события сигнальным, установите равным True значение параметра bInitialState. Для открытия именованного объекта события используется функция OpenEvent, причем это может сделать и другой процесс.

Для управления объектами событий используются следующие три функции:

BOOL SetEvent(HANDLE hEvent)

BOOL ResetEvent(HANDLE hEvent)

BOOL PulseEvent(HANDLE hEvent)

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

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

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

Примечание

Хотя в книгах многих авторов и даже в некоторых документах Microsoft (см. примечания в разделе MSDN, содержащем описание функции PulseEvent) рекомендуется избегать использования функции PulseEvent, лично я считаю эту функцию не только полезной, но и существенно важной, как это следует из обсуждения многочисленных примеров, приведенных в двух следующих главах.

Следует отметить, что функция PulseEvent становится полезной лишь после того, как сбрасываемое вручную событие установлено в сигнальное состояние с помощью функции SetEvent. Будьте внимательны, когда используете функцию WaitForMultipleObjects для ожидания перехода в сигнальное состояние всех событий. Ожидающий поток освободится только тогда, когда одновременно все события будут находиться в сигнальном состоянии, и некоторые из событий, находящихся в сигнальном состоянии, могут быть сброшены, прежде чем поток освободится.

В упражнении 8.5 вам предлагается изменить программу sortMT (программа 7.2) за счет использования в ней событий.

Переменные условий (condition variables) Pthreads в некоторой степени сравнимы с событиями, но используются в сочетании с мьютексами. Такой способ их использования в действительности является очень плодотворным и будет описан в главе 10. Для создания и уничтожения переменной условий используются, соответственно, системные вызовы pthread_cond_init и pthread_cond_destroy. Функциями ожидания являются pthread_cond_wait и pthread_cond_timedwait. Системный вызов pthread_cond_signal осуществляет возврат после освобождения одного ожидающего потока аналогично Windows-функции PulseEvent в случае автоматически сбрасываемых событий, тогда как вызов pthread_cond_broadcast сигнализирует всем ожидающим потокам, и поэтому его можно сопоставить функции PulseEvent, применяемой к сбрасываемому вручную событию. Эквивалентов функций PulseEvent и ResetEvent, используемых в случае сбрасываемых вручную событий, не существует.

Обзор: четыре модели использования событий

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

Предостережение

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

В табл. 8.1 описаны четыре возможные ситуации.

память программный код несинхронизированный поток

Таблица 8.1. Сводная таблица свойств событий

Автоматически сбрасываемые события

Сбрасываемые вручную события

SetEvent

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

Освобождаются все потоки, которые в настоящее время ожидают наступления события. Событие остается в сигнальном состоянии до тех пор, пока не будет сброшено каким-либо потоком.

PulseEvent

Освобождается строго один поток, но только в том случае, если имеется поток, ожидающий наступления события.

Освобождаются все потоки, которые в этот момент ожидают наступления события, если таковые имеются, после чего событие сбрасывается и переходит в несигнальное состояние.

Образно говоря, автоматически сбрасываемое событие -- это дверь, снабженная пружиной, которая обеспечивает автоматическое закрытие двери, в то время как вручную сбрасываемое событие можно уподобить двери, в которой пружина отсутствует и которая, будучи раз открытой, продолжает оставаться в таком состоянии. Используя эту метафору, можно сказать, что функция PulseEvent открывает дверь и закрывает ее сразу же после того, как через нее проходят одна (автоматически сбрасываемые события) или все (вручную сбрасываемые события) ожидающие потоки. Функция SetEvent открывает дверь и освобождает ее.

Пример: система "производитель/потребитель"

В этом примере возможности программы 8.1 расширяются таким образом, чтобы потребитель мог дожидаться момента, когда появится доступное сообщение. Тем самым устраняется одна из проблем, связанная с тем, что в предыдущем варианте программы потребитель должен был непрерывно повторять попытки получения новых сообщений. Результирующая программа (программа 8.2) называется eventPC.

Заметьте, что в предлагаемом решении вместо объектов CRITICAL_SECTION используются мьютексы; единственной причиной для этого послужило лишь желание проиллюстрировать применение мьютексов. В то же время, использование автоматически сбрасываемого события и функции SetEvent в потоке потребителя является весьма существенным для работы программы, поскольку это гарантирует освобождение только одного потока.

Также обратите внимание на способ связывания мьютекса и события со структурой данных блока сообщения. Мьютекс активизирует критический участок кода для доступа к объекту структуры данных, тогда как событие используется для уведомления о том, что появилось новое сообщение. Обобщая, можно сказать, что мьютекс гарантирует сохранение инвариантов объекта, а событие сигнализирует о нахождении объекта в заданном состоянии. Эта базовая методика широко применяется в последующих главах.

Программа 8.2. eventPC: система "производитель/потребитель", использующая сигналы

/* Глава 8. eventPC.с */

/* Поддерживает два потока -- производителя и потребителя. */

/* Производитель периодически создает буферные данные с контрольными */

/* суммами, или "блоки сообщений", сигнализирующие потребителю о готовности*/

/* сообщения. Поток потребителя отображает информацию в ответ на запрос.*/

#include "EvryThng.h"

#include

#define DATA_SIZE 256

typedef struct msg_block_tag { /* Блок сообщения. */

volatile DWORD f_ready, f_stop; /* Флаги готовности и прекращения сообщений. */

volatile DWORD sequence; /* Порядковый номер блока сообщения. */

volatile DWORD nCons, nLost; time_t timestamp;

HANDLE mguard; /* Мьютекс, защищающий структуру блока сообщения. */

HANDLE mready; /* Событие "Сообщение готово". */

DWORD checksum; /* Контрольная сумма сообщения. */

DWORD data[DATA_SIZE]; /* Содержимое сообщения. */

} MSG_BLOCK;

/* … */

DWORD _tmain(DWORD argc, LPTSTR argv[]) {

DWORD Status, ThId;

HANDLE produce_h, consume_h;

/* Инициализировать мьютекс и событие (автоматически сбрасываемое) в блоке сообщения. */

mblock.mguard = CreateMutex(NULL, FALSE, NULL);

mblock.mready = CreateEvent(NULL, FALSE, FALSE, NULL);

/* Создать потоки производителя и потребителя; ожидать их завершения.*/

/* … Как в программе 9.1 … */

CloseHandle(mblock.mguard);

CloseHandle(mblock.mready);

_tprintf(_T("Потоки производителя и потребителя завершили выполнение\n"));

_tprintf(_T("Отправлено: %d, Получено: %d, Известные потери: %d\n"), mblock.sequence, mblock.nCons, mblock.nLost);

return 0;

}

DWORD WINAPI produce(void *arg)

/* Поток производителя -- создание новых сообщений через случайные */

/* интервалы времени. */

{

srand((DWORD)time(NULL)); /* Создать начальное число для генератора случайных чисел. */

while(!mblock.f_stop) {

/* Случайная задержка. */

Sleep(rand() / 10); /* Длительный период ожидания следующего сообщения. */

/* Получить и заполнить буфер. */

WaitForSingleObject(mblock.mguard, INFINITE);

__try {

if (!mblock.f_stop) {

mblock.f_ready = 0;

MessageFill(&mblock);

mblock.f_ready = 1;

mblock.sequence++;

SetEvent(mblock.mready); /* Сигнал "Сообщение готово". */

}

} __finally { ReleaseMutex (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') {

WaitForSingleObject(mblock.mguard, INFINITE);

ShutDown = mblock.f_stop = 1;

ReleaseMutex(mblock.mguard);

} else if (command == 'c') {

/* Получить новый буфер принимаемых сообщений. */

WaitForSingleObject(mblock.mready, INFINITE);

WaitForSingleObject(mblock.mguard, INFINITE);

__try {

if (!mblock.f_ready) _leave;

/* Ожидать наступление события, указывающего на готовность сообщения. */

MessageDisplay(&mblock);

mblock.nCons++;

mblock.nLost = mblock.sequence - mblock.nCons;

mblock.f_ready = 0; /* Новые готовые сообщения отсутствуют. */

} __finally {ReleaseMutex (mblock.mguard);}

} else {

_tprintf(_T("Недопустимая команда. Повторите попытку.\n"));

}

}

return 0;

}

Примечание

Существует вероятность того, что поток потребителя, уведомленный о готовности сообщения, в действительности не успеет обработать текущее сообщение до того, как поток производителя сгенерирует еще одно сообщение до захвата мьютекса потоком потребителя. В результате такого поведения программы поток потребителя может обработать одно и то же сообщение дважды, если бы не проверка, предусмотренная в начале try-блока потребителя. Эта и другие аналогичные проблемы обсуждаются в главе 10.

Обзор: объекты синхронизации Windows

Наиболее важные свойства объектов синхронизации Windows перечислены в табл. 8.2.

Таблица 8.2. Сравнительные характеристики объектов синхронизации Windows

CRITICAL_SECTION

Мьютекс

Семафор

Событие

Именованный защищаемый объект синхронизации

Нет

Да

Да

Да

Доступность из нескольких процессов

Нет

Да

Да

Да

Синхронизация

Вхождение

Ожидание

Ожидание

Ожидание

Освобождение

Выход

Мьютекс может быть освобожден или оставлен без контроля.

Освобождается любым потоком.

Функции SetEvent, PulseEvent.

Права владения

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

В каждый момент времени иметь права владельца может только один поток. Владеющий поток может выполнять функцию ожидания несколько раз, не блокируя свое выполнение.

Понятие владения неприменимо. Доступ разрешен одновременно нескольким потокам, число которых ограничено максимальным значением счетчика.

Понятие владения неприменимо. Функции SetEvent и PulseEvent могут быть вызваны любым потоком.

Результат освобождения

Разрешается вхождение одного потока из числа ожидающих.

Вслед за последним освобождением права владения разрешается приобрести одному потоку из числа ожидающих.

Продолжать выполнение могут несколько потоков, число которых определяется текущим значением счетчика.

После вызова функций SetEvent или PulseEvent продолжать выполнение будет один или несколько ожидающих потоков.

Ожидание сообщений и объектов

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

Дополнительные рекомендации относительно использования мьютексов и объектов CRITICAL_SECTION

К этому времени мы успели познакомиться со всеми объектами синхронизации Windows и исследовали их применимость на ряде примеров. Мьютексы и объекты CS рассматривались первыми, а поскольку события мы еще будем интенсивно использовать в следующей главе, то настоящую главу целесообразно завершить рекомендациями относительно применения мьютексов и объектов CS для обеспечения корректности выполнения, удобства сопровождения и повышения производительности программ.

Приведенные ниже утверждения сформулированы, как правило, в терминах мьютексов, однако, если не оговорено иное, все сказанное относится и к объектам CS.

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

- Если поток завершает выполнение или его выполнение прерывается до того, как он покинет (разблокирует) объект CS, то этот объект остается блокированным. Чрезвычайно полезным свойством мьютексов является то, что владеющий ими поток может завершить выполнение, не уступив прав владения мьютексом.

- Не пытайтесь получить доступ к ресурсам, защищаемым мьютексом, если функция WaitForSingleObject вызвана с использованием конечного интервала ожидания.

- Ожидать перехода блокированного мьютекса в сигнальное состояние могут сразу несколько потоков. Когда мьютекс освобождается, то только один из ожидающих потоков получает права владения мьютексом и переводится в состояние готовности планировщиком ОС на основании действующей стратегии приоритетов и планирования. Не следует делать никаких предположений относительно того, что какой-либо поток будет пользоваться приоритетом; как и в любом другом случае, программу следует проектировать таким образом, чтобы приложение работало корректно независимо от того, какой именно из ожидающих потоков получит права владения мьютексом и возобновит выполнение. Те же замечания остаются справедливыми и в отношении потоков, ожидающих наступления события; никогда не следует предполагать, что при переходе объекта события в сигнальное состояние освободится какой-то определенный поток или что потоки будут разблокированы в какой-то определенной очередности.

- К критическому участку кода относятся все операторы, расположенные между точками программы, в которых поток приобретает права владения мьютексом и уступает их. Для определения нескольких критических участков кода может быть использован один и тот же мьютекс. Корректная организация программы предполагает, что критический участок кода, определяемый мьютексом, в каждый момент времени может выполняться только одним потоком.

- Определяемая мьютексами степень детализации программы, или гранулярность мьютексов (mutex granularity), оказывает влияние на производительность и требует серьезного рассмотрения. Размер каждого критического участка кода ни в коем случае не должен превышать необходимой величины, и мьютекс не должен захватываться на более длительный промежуток времени, чем это необходимо. Использование критических участков кода чрезмерно большого размера, захватываемых на длительные промежутки времени, снижает параллелизм и может оказывать отрицательное влияние на производительность.

- Связывайте мьютекс непосредственно с ресурсом, защиту которого он должен обеспечивать, возможно, с использованием структуры данных. (Именно эта методика задействована в программах 8.1 и 8.2.)

- Максимально точно документируйте инвариант, используя для этого словесные описания либо логические, или булевские, выражения. Инвариант-- это свойство защищаемого ресурса, сохранение которого неизменным вне критического участка кода вы гарантируете. Форма выражения инвариантов может быть самой различной: "элемент принадлежит обоим спискам или не принадлежит ни одному из них", "контрольная сумма данных в буфере является достоверной", "связанный список является действительным" или "0 <= nLost + nCons <= sequence". Точно сформулированные инварианты могут использоваться совместно с макросом ASSERT при отладке программ, хотя оператор ASSERT должен иметь собственный критический участок кода.

- Убедитесь в том, что каждый критический участок кода имеет только одну точку входа, в которой поток блокирует мьютекс, и только одну точку выхода, в которой поток освобождает мьютекс. Избегайте использования сложных операторов ветвления и таких операторов, как break, return или goto, предоставляющих возможность выхода за пределы критического участка кода. Для защиты от подобных рисков оказываются удобными обработчики завершения.

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

Другие функции взаимоблокировки

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

Функции взаимоблокировки настолько же полезны, насколько и эффективны; эти функции реализуются в пользовательском пространстве с применением всего лишь нескольких машинных команд.

Функция InterlockedExchange сохраняет значение одной переменной в другой.

LONG InterlockedExchange(LPLONG Target, LONG Value)

Эта функция возвращает текущее значение переменной, на которую указывает параметр Target, и устанавливает значение этой переменной равным Value. Функция InterlockedExchangeAdd прибавляет второе значение к первому.

LONG InterlockedExchangeAdd(PLONG Addend, LONG Increment)

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

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

PVOID InterlockedCompareExchange(PVOID *Destination, PVOID Exchange, PVOID Comparand)

Эта функция выполняет атомарным образом следующие действия (использование типа данных PVOID для двух последних параметров может казаться вам непонятным):

Temp = *Destination;

if (*Destination == Comparand) *Destination = Exchange;

return Temp;

Одним из вариантов применения этой функции является управление блокировкой с целью реализации критического участка кода. *Destination является переменной блокировки (lock variable), причем значению 1 соответствует разблокированное состояние, а значению 0 -- блокированное. Значение Exchange задается равным 0, a Comparand -- 1. Вызывающему потоку известно, что она владеет критическим участком, если функция возвращает 1. В противном случае вызывающий поток должен "уснуть", или выполнить ожидание в состоянии занятости ("spin"), то есть совершать в течение короткого промежутка времени цикл, в котором ничего не делается, с той только целью, чтобы выждать некоторое время, а затем вновь повторить попытку. По существу, именно такой цикл и выполняет функция EnterCriticalSection, ожидая перехода в сигнальное состояние объекта CRITICAL_SECTION с ненулевым значением спин-счетчика; для получения более подробной информации по этому вопросу обратитесь к главе 9.

Учет факторов производительности при организации управления памятью

Программа 9.1, приведенная в следующей главе, позволяет исследовать различные аспекты производительности в условиях, когда несколько потоков соревнуются между собой за право обладания разделяемыми ресурсами. Аналогичные эффекты будут наблюдаться и в случае, когда потоки привлекаются для управления памятью с использованием функций malloc и free из многопоточной стандартной библиотеки С, поскольку эти функции используют объекты CRITICAL_SECTION для синхронизации доступа к структуре данных кучи (вы можете в этом сами убедиться, просмотрев исходный код библиотеки С). Ниже описаны два возможных способа улучшения производительности.

- Каждый поток, управляющий памятью, может создать дескриптор типа HANDLE для собственной кучи с помощью функции HeapCreate (глава 5). После этого для распределения памяти вместо функций malloc и free можно использовать функции HeapAlloc и HeapFree.

- Значение переменной окружения времени выполнения __MSVCRT_HEAP_SELECT можно установить равным __GLOBAL_HEAP_SELECTED. Это приведет к тому, что функции malloc и free будут использовать для управления памятью схему Windows, которая использует спин-блокировки (spin locks) вместо объектов CS и может быть намного более эффективной. Этот метод был предложен Гербертом Орашем (Gerbert Orasche) в статье "Configuring VC++ Multithreaded Memory Management", опубликованной в майском выпуске журнала Windows Developer's Journal за 2000 год, а представленные в этой статье результаты убедительно свидетельствуют о преимуществах данного метода в отношении производительности.

Глава 2. Взаимодействие между процессами

В главе 6 было показано, как создавать процессы и управлять ими, тогда как главы 7--10 были посвящены описанию методов управления потоками, которые выполняются внутри процессов, и объектов, обеспечивающих их синхронизацию. Вместе с тем, если не считать использования разделяемой памяти, мы до сих пор не рассмотрели ни одного из методов взаимодействия между процессами.

Ниже вы ознакомитесь с последовательным межпроцессным взаимодействием (Interprocess Communication, IPC)[30], в котором используются объекты, подобные файлам. Двумя основными механизмами Windows, реализующими IPC, являются анонимные и именованные каналы, доступ к которым осуществляется с помощью уже известных вам функций ReadFile и WriteFile. Простые анонимные каналы являются символьными и работают в полудуплексном режиме. Эти свойства делают их удобными для перенаправления выходных данных одной программы на вход другой, как это обычно делается в UNIX. В первом примере демонстрируется, как реализовать эту возможность. По сравнению с анонимными каналами возможности именованных каналов гораздо богаче. Они являются дуплексными, ориентированы на обмен сообщениями и обеспечивают взаимодействие через сеть. Кроме того, один именованный канал может иметь несколько открытых дескрипторов. В сочетании с удобными, ориентированными на выполнение транзакций функциями эти возможности делают именованные каналы пригодными для создания клиент-серверных систем. Это демонстрируется во втором из приведенных в настоящей главе примере, представляющем многопоточный клиент-серверный командный процессор, моделируемый в соответствии с рис. 7.1, который привлекался для обсуждения потоков. Каждый из потоков сервера управляет взаимодействием с отдельным клиентом, и для каждой пары "поток/клиент" используется отдельный дескриптор, то есть отдельный экземпляр именованного канала. Наконец, почтовые ящики обеспечивают широковещательную рассылку сообщений по схеме "один многим", а их использование для расширения возможностей командного процессора демонстрируется в последнем примере.

Анонимные каналы

Анонимные каналы (anonymous channels) Windows обеспечивают однонаправленное (полудуплексное) посимвольное межпроцессное взаимодействие. Каждый канал имеет два дескриптора: дескриптор чтения (read handle) и дескриптор записи (write handle). Функция, с помощью которой создаются анонимные каналы, имеет следующий прототип:

BOOL CreatePipe(PHANDLE phRead, PHANDLE phWrite, LPSECURITY_ATTRIBUTES lpsa, DWORD cbPipe)

Дескрипторы каналов часто бывают наследуемыми; причины этого станут понятными из приведенного ниже примера. Значение параметра cbPipe, указывающее размер канала в байтах, носит рекомендательный характер, причем значению 0 соответствует размер канала по умолчанию.

Чтобы канал можно было использовать для IPC, должен существовать еще один процесс, и для этого процесса требуется один из дескрипторов канала. Предположим, например, что родительскому процессу, вызвавшему функцию CreatePipe, необходимо вывести данные, которые нужны дочернему процессу. Тогда возникает вопрос о том, как передать дочернему процессу дескриптор чтения (phRead). Родительский процесс осуществляет это, устанавливая дескриптор стандартного ввода в структуре STARTUPINFO для дочерней процедуры равным *phRead.

Чтение с использованием дескриптора чтения канала блокируется, если канал пуст. В противном случае в процессе чтения будет воспринято столько байтов, сколько имеется в канале, вплоть до количества, указанного при вызове функции ReadFile. Операция записи в заполненный канал, которая выполняется с использованием буфера в памяти, также будет блокирована.

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

Пример: перенаправление ввода/вывода с использованием анонимного канала

В программе 11.1 представлен родительский процесс, который создает два процесса из командной строки и соединяет их каналом. Родительский процесс устанавливает канал и осуществляет перенаправление стандартного ввода/вывода. Обратите внимание на то, каким образом задается свойство наследования дескрипторов анонимного канала и как организуется перенаправление стандартного ввода/вывода на два дочерних процесса; эти методики описаны в главе 6.

Местоположение оператора WriteFile в блоке Program2 на рис. 11.1 справа предполагает, что программа считывает большой объем данных, обрабатывает их, и лишь после этого записывает результаты. Эту запись можно было бы осуществлять и внутри цикла, выводя результаты после каждого считывания.


Рис. 11.1. Межпроцессное взаимодействие с использованием анонимного канала

Дескрипторы каналов и потоков должны закрываться при первой же возможности. На рис. 11.1 закрытие дескрипторов не отражено, однако это делается в программе 11.1.

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

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

В программе 11.1 используется непривычный синтаксис: две команды, разделенные символом =, обозначающим канал. Использование для этой цели символа вертикальной черты (|) привело бы к возникновению конфликта с системным командным процессором. Рисунок 11.1 является схематическим представлением выполнения следующей команды:

$ pipe Program1 аргументы = Program2 аргументы

При использовании средств командного процессора UNIX или Windows соответствующая команда имела бы следующий вид:

$ Program1 аргументы | Program2 аргументы

Программа 11.1. pipe: межпроцессное взаимодействие с использованием анонимных каналов

#include "EvryThng.h"

int _tmain(int argc, LPTSTR argv[])

/* Соединение двух команд с помощью канала в командной строке: pipe команда1 = команда2 */

{

DWORD i = 0;

HANDLE hReadPipe, hWritePipe;

TCHAR Command1[MAX_PATH];

SECURITY_ATTRIBUTES PipeSA = /* Для наследуемых дескрипторов. */

{sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};

PROCESS_INFORMATION ProcInfo1, ProcInfo2;

STARTUPINFO StartInfoCh1, StartInfoCh2;

LPTSTR targv = SkipArg(GetCommandLine());

GetStartupInfo(&StartInfoCh1);

GetStartupInfo(&StartInfoCh2);

/* Найти символ "=", разделяющий две команды. */

while (*targv != '=' && *targv != '\0') {

Command1[i] = *targv;

targv++;

i++;

}

Command1[i] = '\0';

/* Пропуск до начала второй команды. */

targv = SkipArg(targv);

CreatePipe(&hReadPipe, &hWritePipe, &PipeSA, 0);

/* Перенаправить стандартный вывод и создать первый процесс. */

StartInfoCh1.hStdInput = GetStdHandle(STD_INPUT_HANDLE);

StartInfoCh1.hStdError = GetStdHandle(STD_ERROR_HANDLE);

StartInfoCh1.hStdOutput = hWritePipe;

StartInfoCh1.dwFlags = STARTF_USESTDHANDLES;

CreateProcess(NULL, (LPTSTR)Command1, NULL, NULL, TRUE /* Унаследовать дескрипторы. */, 0, NULL, NULL, &StartInfoCh1, &ProcInfo1);

CloseHandle(ProcInfo1.hThread);

/* Закрыть дескриптор записи канала, поскольку он больше не нужен, чтобы вторая команда могла обнаружить конец файла. */

CloseHandle(hWritePipe);

/* Повторить операции (симметричным образом) для второго процесса. */

StartInfoCh2.hStdInput = hReadPipe;

StartInfoCh2.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);

StartInfoCh2.hStdError = GetStdHandle(STD_ERROR_HANDLE);

StartInfoCh2.dwFlags = STARTF_USESTDHANDLES;

CreateProcess(NULL, (LPTSTR)targv, NULL, NULL, TRUE, 0, NULL, NULL, &StartInfoCh2, &ProcInfo2);

CloseHandle(ProcInfo2.hThread);

CloseHandle(hReadPipe);

/* Ожидать завершения первого и второго процессов. */

WaitForSingleObject(ProcInfo1.hProcess, INFINITE);

CloseHandle(ProcInfo1.hProcess);

WaitForSingleObject(ProcInfo2.hProcess, INFINITE);

CloseHandle(ProcInfo2.hProcess);

return 0;

}

Именованные каналы

Именованные каналы (named pipes) предлагают ряд возможностей, которые делают их полезными в качестве универсального механизма реализации приложений на основе IPC, включая приложения, требующие сетевого доступа к файлам, и клиент-серверные системы[31], хотя для реализации простых вариантов IPC, ориентированных на байтовые потоки, как в предыдущем примере, в котором взаимодействие процессов ограничивается рамками одной системы, анонимных каналов вам будет вполне достаточно. К числу упомянутых возможностей (часть которых обеспечивается дополнительно) относятся следующие:

- Именованные каналы ориентированы на обмен сообщениями, поэтому процесс, выполняющий чтение, может считывать сообщения переменной длины именно в том виде, в каком они были посланы процессом, выполняющим запись.

- Именованные каналы являются двунаправленными, что позволяет осуществлять обмен сообщениями между двумя процессами посредством единственного канала.

- Допускается существование нескольких независимых экземпляров канала, имеющих одинаковые имена. Например, с единственной серверной системой могут связываться одновременно несколько клиентов, использующих каналы с одним и тем же именем. Каждый клиент может иметь собственный экземпляр именованного канала, и сервер может использовать этот же канал для отправки ответа клиенту.

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

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

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

Использование именованных каналов

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

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

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

Кроме того, для каждого экземпляра именованного канала сервер создает поток, так что для каждого клиента существует выделенный поток и экземпляр именованного канала. Следовательно, рис. 11.2 показывает, как реализовать модель многопоточного сервера, впервые представленную на рис. 7.1.

Рис. 11.2. Взаимодействие клиентов с сервером через именованные каналы

Создание именованных каналов

Серверами именованных каналов могут быть только системы на основе Windows NT (как обычно, здесь имеются в виду версия 4.0 и последующие); системы на базе Windows 9x могут выступать только в роли клиентов.

Прототип функции CreateNamedPipe представлен ниже.

HANDLE CreateNamedPipe(LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY ATTRIBUTES lpSecurityAttributes)

Параметры

lpName -- указатель на имя канала, который должен иметь следующую форму:

\\.\pipe\[path]pipename

Точка (.) обозначает локальный компьютер; таким образом, создать канал на удаленном компьютере невозможно.

dwOpenMode -- указывает один из следующих флагов:

- PIPE_ACCESS_DUPLEX -- этот флаг эквивалентен комбинации значений GENERIC_READ и GENERIC_WRITE.

- PIPE_ACCESS_INBOUND -- данные могут передаваться только в направлении от клиента к серверу; эквивалентно GENERIC_READ.

- PIPE_ACCESS_OUTBOUND -- этот флаг эквивалентен GENERIC_WRITE.

При задании режима могут также указываться значения FILE_FLAG_WRITE_THROUGH (не используется с каналами сообщений) и FILE_FLAG_OVERLAPPED (перекрывающиеся операции рассматриваются в главе 14).

dwPipeMode -- имеются три пары взаимоисключающих значений этого параметра. Эти значения указывают, ориентирована ли запись на работу с сообщениями или байтами, ориентировано ли чтение на работу с сообщениями или блоками, и блокируются ли операции чтения.

- PIPE_TYPE_BYTE и PIPE_TYPE_MESSAGE -- указывают, соответственно, должны ли данные записываться в канал как поток байтов или как сообщения. Для всех экземпляров каналов с одинаковыми именами следует использовать одно и то же значение.

- PIPE_READMODE_BYTE и PIPE_READMODE_MESSAGE -- указывают, соответственно, должны ли данные считываться как поток байтов или как сообщения. Значение PIPE_READMODE_MESSAGE требует использования значения PIPE_TYPE_MESSAGE.

- PIPE_WAIT и PIPE_NOWAIT -- определяют, соответственно, будет или не будет блокироваться операция ReadFile. Следует использовать значение PIPE_WAIT, поскольку для обеспечения асинхронного ввода/вывода существуют лучшие способы.

nMaxInstances -- определяет количество экземпляров каналов, а следовательно, и количество одновременно поддерживаемых клиентов. Как показано на рис. 11.2, при каждом вызове функции CreateNamedPipe для данного канала должно использоваться одно и то же значение. Чтобы предоставить ОС возможность самостоятельно определить значение этого параметра на основании доступных системных ресурсов, следует указать значение PIPE_UNLIMITED_INSTANCES.

nOutBufferSize и nInBufferSize -- позволяют указать размеры (в байтах) выходного и входного буферов именованных каналов. Чтобы использовать размеры буферов по умолчанию, укажите значение 0.

nDefaultTimeOut -- длительность интервала ожидания по умолчанию (в миллисекундах) для функции WaitNamedPipe, которая обсуждается в следующем разделе. Эта ситуация, в которой функция, создающая объект, устанавливает интервал ожидания для родственной функции, является уникальной.

В случае ошибки возвращается значение INVALID_HANDLE_VALUE, поскольку дескрипторы каналов аналогичны дескрипторам файлов. При попытке создания именованного канала под управлением Windows 9x, которая не может выступать в качестве сервера именованных каналов, возвращаемым значением будет NULL, что может стать причиной недоразумений.

lpSecurityAttributes -- имеет тот же смысл, что и в случае любой функции, создающей объект.

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

Подключение клиентов именованных каналов

Как показано на рис. 11.2, для подключения клиента к именованному каналу применяется функция CreateFile, при вызове которой указывается имя именованного канала. Часто клиент и сервер выполняются на одном компьютере, и в этом случае для указания имени канала используется следующая форма:

\\.\pipe\[path]pipename

Если сервер находится на другом компьютере, для указания имени канала используется следующая форма:

\\servername\pipe\[path]pipename

Использование точки (.) вместо имени локального компьютера в случае, когда сервер является локальным, позволяет значительно сократить время подключения.

Функции состояния именованных каналов

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

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

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

- GetNamedPipeInfo -- определяет, принадлежит ли дескриптор экземпляру клиента или сервера, размеры буферов и тому подобное.

Функции подключения именованных каналов

После создания именованного канала сервер может ожидать подключения клиента (осуществляемого с помощью функции CreateFile или функции CallNamedFile, описанной далее в этой главе), используя для этого функцию ConnectNamedPipe, которая является серверной функцией лишь в случае Windows NT:

Bool ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped)

Если параметр lpOverlapped установлен равным NULL, то функция ConnectNamedPipe осуществляет возврат сразу же после установления соединения с клиентом. В случае успешного выполнения функции возвращаемым значением является TRUE. Если же подключение клиента происходит в течение промежутка времени между вызовами сервером функций CreateNamedPipe и ConnectNamed-Pipe, то возвращаемым значением будет FALSE. В этом случае функция GetLastError вернет значение ERROR_PIPE_CONNECTED.

После возврата из функции ConnectNamedPipe сервер может выполнять чтение запросов с помощью функции ReadFile и запись ответов посредством функции WriteFile. Наконец, сервер должен вызвать функцию DisconnectNamedPipe, чтобы освободить дескриптор (экземпляра канала) для соединения с другим возможным клиентом.

Последняя функция, WaitNamedPipe, используется клиентами для синхронизации соединений с сервером. Функция осуществляет успешный возврат, когда на сервере имеется незавершенный вызов функции ConnectNamedPipe, указывающий на наличие доступного экземпляра именованного канала. Используя WaitNamedPipe, клиент имеет возможность убедиться в том, что сервер готов к образованию соединения, после чего может вызвать функцию CreateFile. Вместе с тем, вызов клиентом функции CreateFile может завершиться ошибкой, если в это же время другой клиент открывает экземпляр именованного канала или дескриптор экземпляра закрывается сервером. При этом неудачного завершения вызванной сервером функции ConnectNamedPipe не произойдет. Заметьте, что для функции WaitNamedPipe предусмотрен интервал ожидания, который, если он указан, отменяет значение интервала ожидания, заданного при вызове серверной функции CreateNamedPipe.

Подключение клиентов и серверов именованных каналов

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

/* Последовательность операций при создании соединения с использованием именованного канала для сервера. */

hNp = CreateNamedPipe("\\\\.\\pipe\\my_pipe", …);

while (… /* Цикл продолжается вплоть до завершения работы сервера.*/) {

ConnectNamedPipe(hNp, NULL);

while (ReadFile(hNp, Request, …) {

WriteFile(hNp, Response, …);

}

DisconnectNamedPipe(hNp);

}

CloseHandle(hNp);

Перейдем к рассмотрению последовательности операций, выполняемых клиентом, в которой клиент прекращает выполнение после завершения работы, давая возможность подключиться к тому же экземпляру именованного канала другому клиенту. Как показано ниже, клиент может соединиться с сервером в сети, если ему известно сетевое имя сервера (ServerName):

/* Последовательность операций при создании соединения с использованием именованного канала для клиента. */

WaitNamedPipe("\\\\ServerName\\pipe\\my_pipe", NMPWAIT_WAIT_FOREVER);

hNp = CreateFile("\\\\ServerName\\pipe\\my_pipe", …);

while (…/*Цикл выполняется до тех пор, пока не прекратятся запросы.*/ {

WriteFile(hNp, Request, …);

ReadFile(hNp, Response);

}

CloseHandle (hNp); /* Разорвать соединение с сервером. */

Обратите внимание, что клиент и сервер состязаются за ресурсы. Прежде всего, клиентский вызов функции WaitNamedPipe завершится ошибкой, если именованный канал к этому моменту еще не был создан сервером; для краткости тестирование успешности выполнения в нашем примере опущено, однако оно включено в примеры программ, доступные на Web-сайте. Далее, в редких случаях вызов CreateFile может быть выполнен еще до того, как сервер вызовет функцию ConnectNamedPipe. В этом случае функция ConnectNamedPipe вернет серверу значение FALSE, однако взаимодействия посредством именованного канала по-прежнему будет функционировать надлежащим образом.

Экземпляр именованного канала является глобальным ресурсом, поэтому, когда клиент разрывает соединение с сервером, к нему может подключиться другой клиент.

Функции транзакций именованных каналов

На рис. 11.2 показана типичная конфигурация клиента, в которой клиент выполняет следующие операции:

- Открывает экземпляр канала, создавая долговременное соединение с сервером и занимая экземпляр канала.

- Периодически посылает запросы и ожидает получения ответов.

- Закрывает соединение.

Встречающуюся здесь последовательность вызовов функций WriteFile и ReadFile можно рассматривать как единую клиентскую транзакцию, и Windows предоставляет соответствующую функцию для каналов сообщений:

BOOL TransactNamedPipe(HANDLE hNamedPipe, LPVOID lpWriteBuf, DWORD cbWriteBuf, LPVOID lpReadBuf, DWORD cbReadBuf, LPDWORD lpcbRead, LPOVERLAPPED lpOverlapped)

Смысл всех параметров здесь должен быть ясен, поскольку данная функция сочетает в себе функции WriteFile и ReadFile, применяемые к дескриптору именованного канала. Указываются как выходной, так и входной буфер, а разыменованный указатель lpcbRead предоставляет размер сообщения. Перекрывающиеся операции (глава 14) возможны, однако в более типичных случаях функция ожидает ответа.

Функция TransactNamedPipe удобна в использовании, однако, как показывает рис. 11.2, она требует создания постоянного соединения, что ограничивает число возможных клиентов[32].

Ниже приводится прототип второй клиентской вспомогательной функции.

BOOL CallNamedPipe(LPCTSTR lpPipeName, LPVOID lpWriteBuf, DWORD cbWriteBuf, LPVOID lpReadBuf, DWORD cbReadBuf, LPDWORD lpcbRead, DWORD dwTimeOut)

Функция CallNamedPipe не требует образования постоянного соединения; вместо этого она создает временное соединение, объединяя в себе выполнение следующей последовательности операций:

CreateFile

WriteFile

ReadFile

CloseHandle

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

Смысл параметров этой функции тот же, что и в случае функции TransactNamedPipe, если не считать того, что вместо дескриптора для указания канала используется его имя. Функция CallNamedPipe выполняется синхронном режиме (отсутствует структура OVERLAPPED). Указываемая при ее вызове длительность периода ожидания (dwTimeOut) (в миллисекундах) относится к соединению, а не транзакции. Параметр dwTimeOut имеет три специальных значения:

- NMPWAIT_NOWAIT

- NMPWAIT_WAIT_FOREVER

- NMPWAIT_USE_DEFAULT_WAIT, которое приводит к использованию интервала ожидания по умолчанию, заданного в вызове функции CreateNamedPipe.

Определение наличия сообщений в именованных каналах

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

BOOL PeekNamedPipe(HANDLE hPipe, LPVOID lpBuffer, DWORD cbBuffer, LPDWORD lpcbRead, LPDWORD lpcbAvail, LPDWORD lpcbMessage)

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

Чтобы определить, имеются ли в канале данные, необходимо проверить значение *lpcbAvail; если данные в канале присутствуют, оно должно быть больше 0. В этом случае параметры lpBuffer и lpcbRead могут иметь значения NULL. Если же буфер определен параметрами lpBuffer и cbBuffer, то значение *lpcbMessage укажет вам, остается ли еще некоторое количество байтов сообщений, которые не умещаются в буфере, что позволяет распределять буфер большего размера, прежде чем осуществлять чтение из именованного канала. Для канала, работающего в режиме считывания байтов, это значение равно 0.

...

Подобные документы

  • Потоки и синхронизация действий, выполняемых разными потоками, их методика. Критические секции, их операции и случаи эффективного применения. События и объекты исключительного владения, операции с ними и их сравнение. Функции по работе с глобальной кучей.

    лекция [17,6 K], добавлен 24.06.2009

  • Обзор операционных систем, обеспечивающих взаимную синхронизацию процессов и потоков. Понятие критической секции и критических данных, описание приема взаимного исключения. Использование блокирующих переменных и семафоров. Объекты-взаимоисключения.

    доклад [26,7 K], добавлен 27.12.2013

  • Функции программного интерфейса операционной системы Windows, предназначенные для работы с семафорами. Средства синхронизации Win32 АРI, основанные на использовании объектов исполнительной системы с дескрипторами. Проблемы при использовании семафоров.

    реферат [67,4 K], добавлен 06.10.2010

  • Сущность понятий: "куча" (пул памяти), связный список, синхронизация потоков; разработка программы, исключающей возможность перекрытия потоков друг другом. Организация связных списков и использование функций API для работы с пулом памяти в ОС Windows.

    курсовая работа [145,3 K], добавлен 11.05.2012

  • Особенности разработки при использовании потоков. Создание, удаление переменных. Свойства, управление потоками. Вызовы для создания мутекс. Причины завершения потока. Методы синхронизации выполнения потоков. Типичная последовательность действий с мутест.

    лекция [160,8 K], добавлен 29.07.2012

  • Общая характеристика потока как последовательности инструкций, которые считывает и исполняет процессор. Анализ преимуществ одно- и многопроцессорности. Особенности реализации потоков и технологий взаимосвязей с микроархитектурой. Средства синхронизации.

    курсовая работа [27,4 K], добавлен 18.05.2013

  • Архитектура многопроцессорных систем с общей шиной и с неоднородным доступом к памяти. Структура кэш памяти. Взаимодействие user space с kernel space. Средства синхронизации ядра Linux. Обход каталогов страниц. Инструментация кода средствами Clang.

    дипломная работа [513,7 K], добавлен 14.11.2017

  • Основные ограничения синхронизации, необходимые для корректного функционирования системы. Добавление в код производителя и потребителя операторов синхронизации для обеспечения ее корректной работы. Сигнал конечного буфера производителя-потребителя.

    курсовая работа [167,0 K], добавлен 05.12.2012

  • Разработка граф-схемы имитационной модели финансовых потоков предприятия и реализация модели программными средствами Pilgrim. Алгоритм моделирования с постоянным шагом. Выполнение моделирования на полученной программе, разработка программного кода.

    курсовая работа [1,8 M], добавлен 22.11.2013

  • Описание общего алгоритма и интерфейса программы. Метод заполнения массива случайными числами. Метод вычисления длины линии между пространственными точками. Создание, синхронизация и завершение потоков. TThread как абстрактный класс, листинг программы.

    курсовая работа [664,0 K], добавлен 08.04.2014

  • Основные функции и процессы подсистемы управления процессами. Диспетчеризация процессов (потоков). Алгоритмы планирования выполнения потоков. Назначение и разновидности приоритетов в операционных системах. Функции подсистемы управления основной памятью.

    презентация [117,7 K], добавлен 20.12.2013

  • Взаимодействие процессов и потоков в операционной системе, основные алгоритмы и механизмы синхронизации. Разработка школьного курса по изучению процессов в операционной системе Windows для 10-11 классов. Методические рекомендации по курсу для учителей.

    дипломная работа [3,2 M], добавлен 29.06.2012

  • Классификация компьютерной памяти. Использование оперативной, статической и динамической оперативной памяти. Принцип работы DDR SDRAM. Форматирование магнитных дисков. Основная проблема синхронизации. Теория вычислительных процессов. Адресация памяти.

    курсовая работа [1,5 M], добавлен 28.05.2016

  • Количественная, сторона процессов обслуживания потоков сообщений в системах распределения информации. Основные задачи теории телетрафика и сведения о методах решения задач. Принципы классификации потоков вызовов. Просеивание потоков и потоки Эрланга.

    реферат [124,6 K], добавлен 18.02.2012

  • Поддержание целостности общих данных, используемые методы и приемы. Проблема критической секции и направления ее разрешения. Аппаратная поддержка синхронизации, классические проблемы и разрешение. Критические области. Синхронизация в Solaris и в Windows.

    презентация [1,5 M], добавлен 24.01.2014

  • Обзор средств и методов реализации многопоточности в Java. Проблемы производительности и латентности (времени реакции). Методы использующиеся при работе с потоками. Запуск потоков, завершение процесса и демоны. Взаимная, активная блокировка и голодание.

    курсовая работа [275,3 K], добавлен 23.08.2014

  • Общие требования охраны труда во время работы, а также в аварийных ситуациях. Использование метрик программного продукта при ревьюировании. Проверка целостности программного кода и анализ потоков данных. Сценарии использования программного продукта.

    отчет по практике [2,0 M], добавлен 28.11.2022

  • Архитектура предприятия как инструмент управления изменениями. Проектирование архитектуры данных по TOGAF. Описание потоков и источников данных. Синхронизация данных по времени. Описание этапов и рекомендации по использованию инструментов проектирования.

    дипломная работа [2,8 M], добавлен 09.09.2017

  • Описание основных целей и рабочих процессов оператора сотовой связи. Шкала оценки важности информации. Построение матрицы ответственности за аппаратные ресурсы. Разработка структурной схемы их взаимодействия между собой и модели информационных потоков.

    практическая работа [336,0 K], добавлен 28.01.2015

  • Определение наиболее надёжного пути передачи 2-х потоков информации за один цикл между шестью коммутаторами с учётом критерия максимальной помехозащищенности. Вычисление коэффициентов целевой функции и системы ограничений. Оптимальный план обмена данными.

    курсовая работа [617,5 K], добавлен 13.01.2015

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.