Я пытаюсь создать большое количество процессов, заставить их ждать семафор, общий для процесса, и освободить их все «сразу», вызвав несколько раз sem_postиз родительского процесса.
Цикл, в котором я повторный вызов sem_post из родительского процесса занимает заметно много времени, причем более ранние итерации цикла (когда большинство процессов все еще ждут семафора), очевидно, являются самыми большими нарушителями с точки зрения времени настенных часов.
Я отследил это до реализации sem_post от Bionic, которая выглядит следующим образом:
Код: Выделить всё
int sem_post(sem_t* sem) {
atomic_uint* sem_count_ptr = SEM_TO_ATOMIC_POINTER(sem);
unsigned int shared = SEM_GET_SHARED(sem_count_ptr);
int old_value = __sem_inc(sem_count_ptr);
if (old_value < 0) {
// Contention on the semaphore. Wake up all waiters.
__futex_wake_ex(sem_count_ptr, shared, INT_MAX);
} else if (old_value == SEM_VALUE_MAX) {
// Overflow detected.
errno = EOVERFLOW;
return -1;
}
return 0;
}
Эта реализация sem_post увеличивает счетчик семафоров на 1 и пробуждает все ожидающие, если старое значение было ниже 0 (то есть были ожидающие).
Важно ли, чтобы операция пробуждения фьютекса будила все ожидающие, и если да, то почему?
Поскольку sem_post всегда увеличивает только число семафоров на 1, разве этого не будет достаточно, чтобы разбудить одного официанта?
Поскольку при такой реализации пробуждение всех официантов только для того, чтобы один из них успешно захватил семафор, создается то, что выглядит как громоподобная проблема стада, и, похоже, является причиной моих проблем с производительностью, потому что ядру приходится планировать большое количество процессов.
Сначала я думал, что пробуждение всех официантов может быть способом помочь гарантировать отсутствие официант получает на неопределенный срок изголодался, пытаясь схватить семафор. Но затем я просмотрел git-историю Bionic, в которой обнаружился коммит 519763265ec0b634bd9c264a0aca034882458ecc. Это изменение, которое привело к пробуждению всех официантов, и сообщение о фиксации предполагает, что это исправление ошибки:
Код: Выделить всё
libc: Fix sem_post() implementation to wake up all waiting threads.
This also allows us to optimize the case where we increment an
uncontended semaphore (no need to call futex_wake() then).
Код: Выделить всё
-/* unlock a semaphore */
+/* Unlock a semaphore */
int sem_post(sem_t *sem)
{
unsigned int shared;
+ int old;
if (sem == NULL)
return EINVAL;
@@ -239,8 +344,16 @@
shared = SEM_GET_SHARED(sem);
ANDROID_MEMBAR_FULL();
- if (__sem_inc(&sem->count) >= 0)
- __futex_wake_ex(&sem->count, shared, 1);
+ old = __sem_inc(&sem->count);
+ if (old < 0) {
+ /* contention on the semaphore, wake up all waiters */
+ __futex_wake_ex(&sem->count, shared, INT_MAX);
+ }
+ else if (old == SEM_MAX_VALUE) {
+ /* overflow detected */
+ errno = EOVERFLOW;
+ return -1;
+ }
return 0;
}
Код: Выделить всё
- : Use private futexes for semaphore implementation,
unless your set 'pshared' to non-0 when calling sem_init().
Also fixed a bug in sem_post() to make it wake up all waiting
threads, instead of one. As a consequence, the maximum semaphore
value is now reduced to 0x3fffffff.
Подробнее здесь: https://stackoverflow.com/questions/797 ... f-just-one
Мобильная версия