Вот что я в итоге сделал (на данный момент это дело чести, я не уйду, пока у меня не будет правильного кода, независимо от того, нужен ли он для поставленной задачи:)).
Создание
Попробуйте открыть [1] существующий набор sem с 3 sems, в случае неудачи попробуйте [2] создать его. Если не удается создать, потому что кто-то уже создал, вернитесь к [1]. Этот цикл в конечном итоге завершится либо с открытым, либо с созданным sem, либо из-за ошибки, с которой я не могу справиться, и в этом случае я беру мяч и иду домой. (У меня также есть предел N итераций, на всякий случай:)).
Одна из 3-х сем - это полезная нагрузка, другая - счетчик ссылок, а третья - блокировка для счетчика ссылок. После того, как [2] блокировка инициализируется до 0, состояние блокировки.
Крепежные
Если набор sem был создан с помощью [2], все 3 sem подаются в semoped [3] от 0 до 1. Полезная нагрузка освобождается, количество ссылок равно 1, блокировка снимается (без отмены).
Если он был открыт с помощью [1], блокировка получается [4] (-1), счетчик ссылок увеличивается (+1) и блокировка снимается (+1). Это заблокирует, если блокировка равна нулю в данный момент. Если этот semop завершается неудачно из-за того, что множество наборов уничтожается в [6], пока мы ожидаем, сохранение не удастся, и мы возвращаемся к [1]. Этот цикл также имеет ограниченное количество итераций.
Высвобождение
Блокировка получена [5] (-1 с ожиданием), счетчик ссылок уменьшен (-1 без ожидания). Если это удастся, то если ref ref теперь равен нулю, набор sem уничтожается. В противном случае [6] блокировка снята (+1). Если получить блокировку не удалось из-за того, что набор sem был уничтожен - ничего не делать.
Между удержанием и освобождением полезная нагрузка используется как обычно.
Помимо сложности и накладных расходов на 2 семафора на набор, есть только одна проблема (теперь я вижу роковой недостаток :)) - когда создатель падает между [2] и [3]. Это приведет к гибели всех клиентов. Я могу использовать синхронизированное ожидание в Linux и убивать осиротевшие семафоры, но OSX, будучи обычным идиотом, не имеет временных операций, так что я вроде как ...
* ... уходит, чтобы написать собственное ядро или что-то ... *