Вопрос про Win32 ## Event ## объект синхронизации - PullRequest
3 голосов
/ 01 декабря 2010

Сначала позвольте мне представить сцену приложения:

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

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

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

Я пытался использовать эти 2 метода

Метод 1

  1. Создатьсобытие ручного сброса;
  2. При изменении состояния сначала вызовите SetEvent, а затем немедленно вызовите ResetEvent.

Метод 2

  1. Создание ручного сбросаevent;
  2. При изменении состояния вызовите PulseEvent.

Оба метода хорошо работают во время теста.Но я думаю, что ни один из них не является надежным, потому что:

Для ## метода 1 ##, возможно, некоторые ожидающие потоки не получат шансов на выполнение до вызова функции ResetEvent.

Для ## метода 2 ## Microsoft заявляет, что PulseEvent ненадежен и его не следует использовать .

Есть ли какое-либо работоспособное решение для этого случая?Любой совет приветствуется.

Ответы [ 3 ]

1 голос
/ 03 декабря 2010

Невозможно безопасно реализовать это с помощью примитивов синхронизации O (1).

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

Чтобы дождаться, пока все приложения обработают новую службу изменения статуса, можно использовать другой набор из N событий и объектов WaitForMultipleObject с bWaitAll ==ПРАВДА.Каждое приложение должно установить соответствующее событие.

Итак, сервис выполняет цикл: наблюдает за изменением состояния, записывает в общую память, устанавливает все события A, ожидает всех событий B, сбрасывает все события B, продолжает цикл.Каждое приложение выполняет цикл: ожидание события A (i), сброс A (i), изменение состояния, установка B (i), продолжение цикла.

Оба события A и B могут быть автоматическитип сброса.Тогда вам не нужно ничего переустанавливать.

Если вы чувствуете себя зеленым и не хотите тратить ресурсы, вы можете использовать какой-то обратный семафор вместо набора событий B.Это может быть реализовано с помощью одного общего счетчика, синхронизированного мьютексом, и одного события (B ') для уведомления службы.Вместо ожидания всего B установите службу в B ', а когда ожидание закончится, установите счетчик на N. Вместо установки события B (i) каждое приложение должно уменьшить счетчик, а если счетчик упадет до нуля, то последнее приложение должноустановить только B '.

Вы не можете обойти множество событий.Проблема не в установке события А, а в сбросе.Сбрасывая событие A (i), приложение не пропустит очередное изменение статуса.И здесь не полезно использовать семафор.

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

1 голос
/ 01 декабря 2010

Одна из проблем метода № 1 (даже если это маловероятно по времени) состоит в том, что вы не можете гарантировать, что приложение «будет выпущено только один раз» - это будет возможно для потока, ожидающего событиечтобы быть освобожденным, когда вы вызываете SetEvent(), сделайте это, затем попробуйте снова подождать событие, прежде чем ваш поток сможет дозвониться до ResetEvent().I ResetEvent () еще не произошло, поток не будет блокироваться (по сути, он освобождается более одного раза).

Вам может понадобиться второй объект события, который находится в состоянии без сигналов, чтобы потоки ожидали во время выполнения.последовательность SetEvent () / ResetEvent (), которая возникает в вашем «реальном» объекте события.По сути, каждый потребительский поток должен ожидать обоих событий последовательно, и в любой момент сигнализируется только одно из 2 событий.Обычно только одно из событий будет в состоянии сброса, в котором потоки будут блокироваться, но в течение последовательности SetEvent () / ResetEvent () объекта основного события будет короткий период, когда оба события будут в состоянии сброса.

Однако одна важная вещь, которую вы должны принять во внимание, это требование, что «каждое ожидающее приложение будет выпущено».Как ваше общее приложение будет иметь дело с потоком, который как раз собирался блокировать событие изменения статуса, но еще не получил его (так что на самом деле это еще не приложение, ожидающее выхода)?Чем это отличается от того, что SetEvent() не гарантирует, что все потоки, заблокированные в событии, будут запущены при сигнале события?В любом случае, у вас какое-то состояние гонки.

0 голосов
/ 11 мая 2017

Именованные каналы - ваш выбор в этом сценарии!

На именованных каналах

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

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

События автоматического сброса (или как избавиться от ResetEvent и Pulse Event)

Если вы все еще заинтересованы в событиях, здесь мы идем. ☺

Как вы правильно указали, PulseEvent вообще не должен использоваться. Эта функция существует с самой первой 32-разрядной версии Windows NT - 3.1. Может быть, это было надежно тогда, но не сейчас.

Пожалуйста, рассмотрите возможность использования именованных событий автоматического сброса. Просто чтобы повторить о событиях автоматического сброса и о том, как их использовать: функция CreateEvent имеет аргумент bManualReset (если этот параметр имеет значение TRUE, функция создает объект события с ручным сбросом, который требует использования функции ResetEvent для установки Событие для состояния не сигнализируется - это не то, что вам нужно). Если этот параметр имеет значение FALSE, функция создает объект события с автосбросом, и система автоматически сбрасывает состояние события как не сигнализированное после освобождения одного ожидающего потока, то есть выхода из функции, такой как WaitForMultipleObjects или WaitForSigleObject. С событием автосброса вы никогда не вызовете ResetEvent (так как он автоматически сбрасывается) или PulseEvent (так как это ненадежно и не рекомендуется).

Как вы правильно указали, даже Microsoft признала, что PulseEvent не должен использоваться - см. https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx - будут уведомлены только те потоки, которые находятся в состоянии «ожидания» в тот момент, когда PulseEvent находится в называется. Если они находятся в каком-либо другом состоянии, они не будут уведомлены, и вы никогда не узнаете наверняка, в каком состоянии находится поток. Поток, ожидающий объекта синхронизации, может быть на мгновение удален из состояния ожидания с помощью асинхронного вызова процедуры в режиме ядра, а затем возвращен в состояние ожидания после завершения APC. Если вызов PulseEvent происходит в то время, когда поток был удален из состояния ожидания, поток не будет освобожден, поскольку PulseEvent освобождает только те потоки, которые ожидают в момент его вызова. Вы можете узнать больше об асинхронных вызовах процедур в режиме ядра (APC) по следующим ссылкам:

Мы никогда не использовали PulseEvent в наших приложениях - нам удается делать все с помощью только события автоматического сброса - мы используем их начиная с Windows NT 3.51, и они работают очень хорошо, мы очень довольны ими.

Как уведомлять ожидающие заявки

Yoу вас есть несколько потоков в нескольких процессах, которые должны быть уведомлены.Они ждут события, и вы должны убедиться, что все потоки действительно получили уведомление и получили только один раз.Нет другого надежного способа сделать это, кроме (сюрприз!) Использования событий автоматического сброса.Просто создайте собственное именованное событие для каждого ожидающего приложения.Таким образом, вам нужно иметь столько событий, сколько ожидающих приложений.Кроме того, вам нужно будет вести список зарегистрированных потребителей, где каждому потребителю соответствует имя события.Таким образом, чтобы уведомить всех потребителей, вам нужно выполнить SetEvent в цикле для всех событий потребителя.Это очень быстрый, надежный и дешевый способ.Поскольку вы используете межпроцессное взаимодействие, ожидающее приложение должно будет регистрировать и отменять регистрацию своих событий с помощью других средств межпроцессного взаимодействия, таких как SendMessage, или более сложным способом, как ваш объект FileMap.Позвольте мне для простоты привести пример использования SendMessage для этой цели.Когда ожидающее приложение регистрируется в основном процессе уведомлений, оно отправляет SendMessage вашему процессу, чтобы запросить уникальное имя события.Вы просто увеличиваете счетчик и возвращаете что-то вроде YourAppNameEvent1, YourAppNameEvent2 и т. Д. Прежде чем вернуть это имя, вызовите CreateEvent для фактического создания и создания автосброса именованных событий с этим именем, чтобы ожидающие приложения вызывали OpenEvent с этим именем, чтобы открытьсуществующее событие и получить дескриптор события этого объекта события.Когда ожидающее приложение отменяет регистрацию - оно закрывает дескриптор события, которое оно открыло, и отправляет еще одно SendMessage, чтобы вы знали, что вы должны также закрыть CloseHandle на вашей стороне, чтобы окончательно освободить этот объект события (оно закрыто на последнем CloseHandle - событиеобъект уничтожается, когда его последний дескриптор был закрыт).Если произойдет сбой пользовательского процесса, вы получите фиктивное событие, поскольку вы не будете знать, что вам следует делать CloseHandle, но это не должно быть проблемой - события очень быстрые и очень дешевые, и на них практически нет ограничений.объекты ядра - лимит для каждого процесса на дескрипторы ядра составляет 2 ^ 24.Это может быть временным решением - чтобы убедиться, что все работает, но тогда вам следует изменить логику программы, чтобы клиенты создавали события, а вы открывали их перед вызовом SetEvent и последующим закрытием.Если они не открываются - значит, клиент потерпел крах, и вы просто удалите его из списка.Это немного более медленный метод, если вы делаете несколько уведомлений в секунду, но вы можете закрывать события не каждый раз, а только по истечении определенного времени с момента его закрытия в последний раз - вы решаете.Если вы редко уведомляете, вы можете открывать и закрывать каждый раз.

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