Существует ли UNIX/pthreads, эквивалентный событиям ручного сброса Windows?

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

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

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

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

Случай с выпуском особенно плох в многопроцессорной системе, учитывая, что одновременный выпуск всех ожидающих гарантирует, что разница между condvar и событием Windows будет заметна - с событием в N потоков станет доступным для выполнения в системе с N ЦП. , и могут работать параллельно, в то время как с condvar — даже с реализацией, которая избегает громоподобного стада — потоки могут просачиваться только по одному через связанный мьютекс.

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


person BeeOnRope    schedule 08.12.2009    source источник
comment
Возможно, более простой способ подумать об этом: ручной сброс — это событие, похожее на ворота. Он либо открыт, либо закрыт - все потоки блокируются, или ни один поток не блокируется. Прохождение через ворота не влияет на ворота - ворота управляются только явными командами открытия ворот (SetEvent) и закрытия ворот (ResetEvent).   -  person BeeOnRope    schedule 09.12.2009
comment
возможный дубликат кроссплатформенного эквивалента событиям Windows   -  person jww    schedule 11.02.2014


Ответы (5)


Это только для Linux (я упоминаю об этом только потому, что у вас есть тег «linux»), но я думаю, что вы могли бы создать что-то подобное на системном вызове futex. Подробнее см. в статье Ульриха Дреппера о фьютексах.

Грубо говоря, я представляю что-то вроде

void inline gate_wait(volatile int *gate)
{
    if (*gate)
        while (futex(gate, FUTEX_WAIT, 1, 0, 0, 0) == EINTR)
            ;
}

int inline gate_open(volatile int *gate)
{
    *gate = 0;
    return futex(gate, FUTEX_WAKE, INT_MAX, 0, 0, 0);
}

void inline gate_close(volatile int *gate)
{
    *gate = 1;
}

Если вы строите этот примитив синхронизации «ворота» поверх фьютексов, возможно, стоит внести его в библиотеку фьютексов пользовательского пространства Расти Рассела.

person caf    schedule 08.12.2009
comment
Спасибо за это - мысль пересекла мою находку, что Futexes (Futices?) может быть достаточно общим, чтобы быть половиной реализации с нуля на стороне ядра. Я также проверю библиотеку Рассела (мне нужна быстрая rwlock с особым поведением). - person BeeOnRope; 09.12.2009
comment
(и да, я пометил это Linux, а не UNIX, потому что именно там в любом случае появляются новые вещи в этой области) - person BeeOnRope; 09.12.2009
comment
Принятие этого, потому что реальный ответ, кажется, нет, и это дает инструкции о том, как его сделать. - person BeeOnRope; 24.10.2011

если все операции установки/сброса выполняются в одном потоке, вы можете легко использовать канал: «событие» не сигнализируется/сигнализируется, когда канал пуст/не пуст. Чтобы заблокировать «событие», используйте выбор или опрос на считываемом конце канала. Чтобы установить «событие», запишите байт в конец записи. Чтобы сбросить «событие», просто очистите канал, прочитав байт с конца чтения.

person lucho    schedule 30.08.2011
comment
Я думал так же, как и вы, но позже обнаружил проблему: потоки отправки сигнала и пробуждения не являются атомарными. Если поток, отправляющий сигнал, помещает байт в канал, а затем немедленно получает этот байт из канала, ожидающий поток (выполняющий select на стороне чтения канала) может быть разбужен или не разбужен. Я пробовал на openSUSE 12.3(x86), ядро ​​3.7.10. Это поведение не совсем совпадает с событием ручного сброса Windows. - person Jimm Chen; 28.08.2014

Будет ли работать блокировка чтения-писателя? Я подозреваю, что это так, но вам нужно проверить, было ли это эффективно в интересующих вас случаях.

См. сведения о rwlock на странице http://www.opengroup.org/onlinepubs/000095399/basedefs/pthread.h.html

Любое количество считывателей эксклюзивно только с одним писателем, поэтому, чтобы заблокировать другие потоки, используйте блокировку записи. Чтобы позволить им продолжить, разблокируйте. Вы должны увидеть, какой случай более эффективен: получение блокировки чтения, когда уже есть хотя бы одна блокировка чтения, или получение первой блокировки чтения. Я бы предположил первое, поэтому после того, как все потоки будут разблокированы записью, вы можете получить блокировку чтения.

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

О да, идея caf решает эту проблему: ворота read_lock; read_unlock;, поэтому вы не создаете блокировки чтения.

person Peter Cordes    schedule 09.12.2009
comment
Что-то вроде: gate_wait { read_lock; read_unlock }, gate_close { write_lock; }, gate_open { write_unlock; }? Это должно сработать. - person caf; 09.12.2009
comment
Да, хороший звонок. Я не подумал, когда нужно использовать read_unlock. Мне тоже кажется, что должно работать. gate_open может взять read_lock, если это сделает потоки, проходящие через ворота, более эффективными. (поскольку тогда разблокировщику не нужно проверять, были ли какие-либо писатели, ожидающие, когда счетчик read_lock упадет до 0.) - person Peter Cordes; 09.12.2009
comment
Хех, интересное рекурсивное решение, поскольку мой первоначальный запрос был частью переноса высокопроизводительной реализации rwlock с платформ Windows на платформы UNIX. Строго говоря, я думаю, что предоставленное вами решение неправильно сформировано, поскольку поток, который выполняет блокировку записи, также должен выполнять разблокировку в POSIX: результаты не определены, если блокировка чтения-записи rwlock не удерживается вызывающим потоком. Конечно, многие реализации разрешают это без жалоб, но другие могут не делать этого или даже прилагать все усилия, чтобы обеспечить это с помощью явной проверки. - person BeeOnRope; 09.12.2009
comment
У меня сложилось впечатление, что существует один поток привратника, который блокирует и разблокирует другие потоки. Тогда вам лучше взглянуть на futex, так как это собственный интерфейс блокировки Linux, на котором построен материал posix. - person Peter Cordes; 09.12.2009
comment
Функции привратника могут вызываться в любом потоке — в моем случае они фактически вызываются произвольными потоками, без связи между открытием и закрытием ворот. - person BeeOnRope; 09.12.2009

Я полагаю, что вы ищете функции pthread_cond_* в вашей библиотеке pthreads. Pthread Cond Functions

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

person Will    schedule 22.11.2010
comment
В вопросе обсуждаются переменные состояния, которые явно не подходят. - person Hasturkun; 23.11.2010
comment
@Hasturkun: хм, пропустил это, а также тот факт, что вопросу было больше года. :) Вот что я получаю за просмотр вопроса. Просто перечитайте это, и да, как сказал ОП, есть еще накладные расходы. - person Will; 23.11.2010
comment
Да, я хорошо знал об условных переменных, но они не совсем эквивалентны — на самом деле они могут предлагать расширенный набор функций с точки зрения непроизводительности, но при рассмотрении фактического поведения потока они не могут делать то же самое. . - person BeeOnRope; 13.01.2011

В зависимости от требуемой функциональности события, эта статья рекомендует либо условия pthread, либо семафоры POSIX.

person WaffleSouffle    schedule 18.03.2013