#c #semaphore #c 20 #libc #stdatomic
#c #семафор #c 20 #libc #stdatomic
Вопрос:
libc counting_semaphore::release
:
void release(ptrdiff_t __update = 1)
{
if(0 < __a.fetch_add(__update, memory_order_release))
;
else if(__update > 1)
__a.notify_all();
else
__a.notify_one();
}
Уведомляет, только если внутренний счетчик был равен нулю перед приращением, уведомляет более одного официанта, только если приращение больше единицы.
libc counting_semaphore::acquire
:
void acquire()
{
auto const __test_fn = [=]() -> bool {
auto __old = __a.load(memory_order_relaxed);
return (__old != 0) amp;amp; __a.compare_exchange_strong(__old, __old - 1, memory_order_acquire, memory_order_relaxed);
};
__cxx_atomic_wait(amp;__a.__a_, __test_fn);
}
Ожидает, что count будет ненулевым, и пытается преобразовать его в CAS с уменьшенным значением.
Теперь, пожалуйста, ознакомьтесь со следующим 3-потоковым случаем:
counting_semaphore<10> s;
T1: { s.acquire(); /*signal to T3*/ }
T2: { s.acquire(); /*signal to T3*/ }
T3: { /*wait until both signals*/ s.release(1); s.release(1); }
Изначально:
__a == 0
( desired
параметр передается как 0, любая попытка acquire
блокируется)
Временная шкала
T1: enters wait
T2: enters wait
T3: fetch add 1 amp; returns 0, now __a == 1
T3: (0 < 0) is false, so notify_one happens
T1: unblocks from wait
T3: fetch add 1 amp; returns 1, now __a == 2
T3: (0 < 1) is true, so no notification
T1: loads 2
T1: cas 2 to 1 successfully
T1: returns from acquire
T2: still waits despite __a == 1
Похоже ли это на допустимую взаимоблокировку?
почему я спрашиваю здесь, а не сообщаю о проблеме?
Я сообщил о проблеме довольно давно, ответа пока нет. Я хочу понять, действительно ли существует взаимоблокировка или я что-то упускаю.
Комментарии:
1. Я бы сказал, предполагая соответствующие барьеры памяти, действительность этой реализации полностью зависит от того, что стоит за
notify_one
и__cxx_atomic_wait
. Вполне возможно, что реализацияnotify_one
останавливает выполнение до завершения любых обратных вызовов, о__cxx_atomic_wait
которых были уведомлены. Но поскольку эти внутренние вещи являются … внутренними, только разработчик может знать так или иначе.2. @NicolBolas, реализация, которая будет блокировать,
notify_one
нежизнеспособна какstd::atomic::notify_one
реализация, а также, поскольку мыdesired
установили значение 0, а параметр шаблона равен 10, мы можем выполнить до 10release
вызовов раньше, чем acquire , и ни один из них не должен блокироваться.3. » реализация, которая будет блокировать в notify_one » Что вы подразумеваете под «блоком»? Реализация просто выполнит функтор (ы) обратного вызова atomic_wait . Это конечная операция, так почему она должна считаться «блокирующей»? Это также деталь реализации, поэтому выбор дилера.
4. Ах, вижу вашу точку зрения, я неправильно ее понял. Я проверил
__cxx_atomic_wait
, и, похоже, он не останавливаетсяnotify_one
, так как возможно, что он выполнит ожидание ОС__libcpp_atomic_wait
, которое является обычным futexsyscall(SYS_futex, __ptr, FUTEX_WAIT_PRIVATE, __val, amp;__timeout, 0, 0);
. Так что, по-видимому, нет,notify_one
не синхронизирован с функтором в__cxx_atomic_wait
Ответ №1:
Условие if (0 < ...)
— это проблема, но это не единственная проблема.
Заявлено, что последствия release
:
Атомарно выполнить счетчик = обновить. Затем разблокирует все потоки, которые ожидают, что счетчик будет больше нуля.
Обратите внимание на слова «любые потоки». Множественное число. Это означает, что, даже если конкретное update
значение оказалось равным 1, все потоки, заблокированные по этому условию, должны быть уведомлены. Таким образом, вызов notify_one
является неправильным, если только реализация notify_one
always не разблокирует все ожидающие потоки. Что было бы … интересной реализацией этой функции.
Даже если вы измените notify_one
на notify_all
, это не решит проблему. Логика условия в основном предполагает, что уведомление потока должно происходить только в том случае, если все потоки (логически), уведомленные предыдущим release
, завершили свои acquire
вызовы. Стандарт не требует ничего подобного.
Комментарии:
1. Я думаю, что не уведомлять какие-либо потоки, а только один поток, было бы нормально в соответствии с правилом as if , если можно доказать, что ни при каких обстоятельствах нельзя наблюдать разницу в поведении, но, видимо, это не сработало.