Подтвердить что ты не робот

Подождите несколько переменных условия на Linux без лишних снов?

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

  • Подождите один поток на каждой переменной состояния, которую вы хотите подождать, которая, когда woken будет сигнализировать одну переменную условия, которую вы ждете, вместо этого.

  • Переключение между несколькими переменными условия с ожидаемым временем ожидания.

  • Вместо этого создайте фиктивные байты для файлов или труб и опросите их.

# 1 и # 2 не подходят, потому что они вызывают ненужный спать. С# 1 вам нужно дождаться, когда поток фиктивного типа проснется, а затем сообщит реальный поток, а затем, чтобы реальный поток проснулся, вместо реального потока, который просто просыпался для начала - дополнительный квант планировщика, потраченный на это действительно важно для моего приложения, и я бы предпочел не использовать полноценную RTOS. # 2 еще хуже, вы потенциально тратите время ожидания N *, или ваш таймаут будет 0, и в этом случае вы никогда не будете спать (бесконечно горящий процессор и голодные другие потоки также плохие).

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

Есть ли лучший способ сделать это? Любопытно, что ответы также подходят для Solaris.

4b9b3361

Ответ 1

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

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pthread_cond_t var;
static pthread_mutex_t mtx;

unsigned event_flags = 0;
#define FLAG_EVENT_1    1
#define FLAG_EVENT_2    2

void signal_1()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_1;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void signal_2()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_2;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void* handler(void*)
{
    // Mutex is unlocked only when we wait or process received events.
    pthread_mutex_lock(&mtx);

    // Here should be race-condition prevention in real code.

    while(1)
    {
        if (event_flags)
        {
            unsigned copy = event_flags;

            // We unlock mutex while we are processing received events.
            pthread_mutex_unlock(&mtx);

            if (event_flags & FLAG_EVENT_1)
            {
                printf("EVENT 1\n");
                event_flags ^= FLAG_EVENT_1;
            }

            if (event_flags & FLAG_EVENT_2)
            {
                printf("EVENT 2\n");
                event_flags ^= FLAG_EVENT_2;

                // And let EVENT 2 is signal to close.
                // In this case for consistency we break with locked mutex.
                pthread_mutex_lock(&mtx);
                break;
            }

            // Note we should have mutex locked at the iteration end.
            pthread_mutex_lock(&mtx);
        }
        else
        {
            // Mutex is locked. It is unlocked while we are waiting.
            pthread_cond_wait(&var, &mtx);
            // Mutex is locked.
        }
    }

    // ... as we are dying.
    pthread_mutex_unlock(&mtx);
}

int main()
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&var, NULL);

    pthread_t id;
    pthread_create(&id, NULL, handler, NULL);
    sleep(1);

    signal_1();
    sleep(1);
    signal_1();
    sleep(1);
    signal_2();
    sleep(1);

    pthread_join(id, NULL);
    return 0;
}

Ответ 2

Ваш вариант №3 (вместо написания фиктивных байтов для файлов или каналов и опроса на них) имеет лучшую альтернативу в Linux: eventfd.

Вместо буфера ограниченного размера (как в трубе) или бесконечно растущего буфера (как в файле) с eventfd у вас есть 64-разрядный счетчик без знака в ядре. 8-байтовый write добавляет число к счетчику; 8-байтовый read либо обнуляет счетчик и возвращает его предыдущее значение (без EFD_SEMAPHORE), либо уменьшает счетчик на 1 и возвращает 1 (с EFD_SEMAPHORE). Дескриптор файла считается читаемым для функций опроса (select, poll, epoll), когда счетчик отличен от нуля.

Даже если счетчик близок к 64-битовому пределу, write будет просто терпеть неудачу с EAGAIN, если вы сделаете дескриптор файла неблокирующимся. То же самое происходит с read, когда счетчик равен нулю.

Ответ 3

Для ожидания нескольких переменных условий для Solaris существует реализация, которую вы могли бы подключить к Linux, если вам интересно: WaitFor API

Ответ 4

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

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

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

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

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


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

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