Каковы альтернативы функции Win32 PulseEvent ()? - PullRequest
8 голосов
/ 28 апреля 2010

В документации для функции Win32 API PulseEvent() (kernel32.dll) говорится, что эта функция «… ненадежна и не должна использоваться новыми приложениями. Вместо этого используйте условные переменные ». Однако условные переменные нельзя использовать через границы процесса, как (именованные) события.

У меня есть сценарий межпроцессного, межпроцессного выполнения (нативный и управляемый код), в котором у одного производителя иногда есть что-то интересное, чтобы довести его до нуля или более потребителей. Прямо сейчас известное именованное событие используется (и устанавливается в сигнальное состояние) производителем, использующим эту функцию PulseEvent, когда ему нужно сделать что-то известное. Ноль или более потребителей ждут этого события (WaitForSingleObject()) и выполняют действия в ответ. В моем сценарии нет необходимости в двусторонней связи, и производителю не нужно знать, есть ли у события прослушиватели, и при этом Мне нужно знать, было ли событие успешно выполнено. С другой стороны, я не хочу, чтобы какие-либо потребители когда-либо пропускали какие-либо события. Другими словами, система должна быть абсолютно надежной - но производитель делает не нужно знать, так ли это на самом деле. Сценарий можно рассматривать как «тикер часов» - т. е. производитель предоставляет полурегулярный сигнал для подсчета нуля или нескольких потребителей. И все потребители должны имеет правильный счет за любой заданный период времени. Опрос потребителей не допускается (из соображений производительности). Тикер составляет всего несколько миллисекунд (около 20, но не совсем регулярно).

Raymen Chen (The Old New Thing) имеет сообщение в блоге , указывающее на «в корне ошибочную» природу функции PulseEvent (), но я не вижу альтернативы для моего сценария от Chen или оставил комментарий.

Может кто-нибудь предложить один?

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

Ответы [ 5 ]

6 голосов
/ 28 апреля 2010

Я думаю, вам понадобится что-то более сложное, чтобы достичь цели по надежности.

Мое понимание вашей проблемы состоит в том, что у вас есть один производитель и неизвестное количество потребителей, которые все являются разными процессами. Каждый потребитель НИКОГДА не может пропустить ни одного события.

Я хотел бы получить больше разъяснений относительно того, что означает пропущенное событие.

i) если потребитель начал работать и добрался до того, как он дождался вашего метода уведомления и произошло событие, должен ли он обработать его, даже если он не был полностью готов в момент отправки уведомления? (то есть когда потребитель считается активным? когда он запускается или когда обрабатывает свое первое событие)

ii) аналогично, если потребитель обрабатывает событие, а код, ожидающий следующего уведомления, еще не начал свое ожидание (я предполагаю структуру кода Wait -> Process -> Loop to Wait), тогда он должен знать, что произошло другое событие пока он зацикливался?

Я бы предположил, что i) «не совсем», так как это гонка между запуском процесса и «готовностью», и ii) «да»; то есть уведомления, по сути, ставятся в очередь для каждого потребителя, как только он присутствует, и каждый потребитель получает возможность использовать все события, которые создаются, пока он активен и не может их пропустить.

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

1 2 3 4 5 6 7 8 9 0

и потребитель а) запускается и обрабатывает 3, он также должен обрабатывать 4-0

если потребитель b) запускается и обрабатывает 5, но выключается после 9, то он должен был обработать 5,6,7,8,9

если потребитель c) работал, когда начались уведомления, он должен был обработать 1-0

и т.д.

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

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

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

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

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

2 голосов
/ 28 апреля 2010

С PulseEvent есть две присущие проблемы:

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

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

Основным преимуществом этого подхода является то, что вам не нужно явно блокировать ваш поток.Недостатком этого подхода является то, что ваши слушатели должны быть STA (не может иметь очередь сообщений в потоке MTA).

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

Вы также можете убедиться, что вы используете события ручного сброса (так что все ожидающие потоки пробуждаются) и делаете SetEvent /ResetEvent с небольшой задержкой (скажем, 150 мс), чтобы дать больше шансов для временно разбудившихся APC потоков забрать ваше событие.

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

2 голосов
/ 28 апреля 2010

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

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

Я не уверен, что описал вышеизложенное очень хорошо, но, надеюсь, это даст вам представление:)

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

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

Именованная Труба - Король

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

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

О событиях

Если вы все еще заинтересованы в событиях - событие автоматического сброса - король! ☺

Функция CreateEvent имеет аргумент bManualReset. Если этот параметр имеет значение TRUE, функция создает объект события с ручным сбросом, для чего требуется использовать функцию ResetEvent, чтобы установить состояние события как не сигнализированное. Это не то, что вам нужно. Если этот параметр имеет значение FALSE, функция создает объект события с автосбросом, и система автоматически сбрасывает состояние события как не сигнализированное после освобождения одного ожидающего потока.

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

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

Создайте события следующим образом:

EventHandle := CreateEvent(nil, FALSE, FALSE, nil);

Дождаться события из одного потока и выполнить SetEvent из другого потока. Это очень просто и очень надежно.

Никогда не вызывайте ResetEvent (так как он автоматически сбрасывается) или PulseEvent (так как это ненадежно и не рекомендуется). Даже Microsoft признала, что PulseEvent не следует использовать. См https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx

Эта функция ненадежна и не должна использоваться, потому что только те потоки будут уведомлены, которые находятся в состоянии «ожидания» в момент вызова PulseEvent. Если они находятся в каком-либо другом состоянии, они не будут уведомлены, и вы никогда не узнаете наверняка, в каком состоянии находится поток. Поток, ожидающий объекта синхронизации, может быть на мгновение удален из состояния ожидания с помощью асинхронного вызова процедуры в режиме ядра, а затем возвращен в состояние ожидания после завершения APC. Если вызов PulseEvent происходит в то время, когда поток был удален из состояния ожидания, поток не будет освобожден, поскольку PulseEvent освобождает только те потоки, которые ожидают в момент его вызова.

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

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

Ваш межпроцессный сценарий

UnК счастью, ваш случай немного сложнее. У вас есть несколько потоков в нескольких процессах, ожидающих событие, и вы должны убедиться, что все потоки действительно получили уведомление. Нет другого надежного способа, кроме как создать собственное событие для каждого потребителя. Таким образом, вам нужно будет провести столько же мероприятий, сколько и потребителей. Кроме того, вам нужно будет вести список зарегистрированных потребителей, где каждому потребителю соответствует имя события. Таким образом, чтобы уведомить всех потребителей, вам нужно выполнить SetEvent в цикле для всех событий потребителя. Это очень быстрый, надежный и дешевый способ. Поскольку вы используете межпроцессное взаимодействие, потребители должны будут регистрировать и отменять его события с помощью других средств межпроцессного взаимодействия, таких как SendMessage. Например, когда процесс-потребитель регистрируется в основном процессе уведомлений, он отправляет SendMessage вашему процессу, чтобы запросить уникальное имя события. Вы просто увеличиваете счетчик и возвращаете что-то вроде Event1, Event2 и т. Д. И создаете события с этим именем, чтобы потребители открывали существующие события. Когда потребитель отменяет регистрацию - он закрывает дескриптор события, который он открыл ранее, и отправляет еще одно SendMessage, чтобы вы знали, что вы должны также закрыть CloseHandle, чтобы наконец выпустить этот объект события. Если произойдет сбой пользовательского процесса, вы получите фиктивное событие, поскольку вы не будете знать, что вам следует делать CloseHandle, но это не должно быть проблемой - события очень быстрые и очень дешевые, и на них практически нет ограничений. объекты ядра - лимит для каждого процесса на дескрипторы ядра составляет 2 ^ 24. Если вы все еще обеспокоены, вы можете сделать наоборот: клиенты создают события, а вы их открываете. Если они не откроются - значит, произошел сбой клиента, и вы просто удалите его из списка.

0 голосов
/ 28 апреля 2010

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

Если вам нужно разрешить несколько потоков, вы можете использовать именованный семафор с CreateSemaphore . Каждый вызов ReleaseSemaphore увеличивает количество. Например, если счетчик равен 3 и 3 потока ожидают его, все они будут работать.

...