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

Сигнал и порядок разблокировки

void WorkHandler::addWork(Work* w){
    printf("WorkHandler::insertWork Thread, insertWork locking \n");
    lock();
    printf("WorkHandler::insertWork Locked, and inserting into queue \n");
    m_workQueue.push(w);
    signal();
    unLock();
}

Я последовал за учебником, и я понял это. Мне было интересно, нормально ли менять порядок singal() и unLock(), как этот

void WorkHandler::addWork(Work* w){
    printf("WorkHandler::insertWork Thread, insertWork locking \n");
    lock();
    printf("WorkHandler::insertWork Locked, and inserting into queue \n");
    m_workQueue.push(w);
    unLock();
    signal();
}

Если я не могу этого сделать, не могли бы вы рассказать мне подробности, почему мне не разрешено это делать? Спасибо заранее.

4b9b3361

Ответ 1

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

pthread_mutex_lock(mutex);
while (!predicate)
  pthread_cond_wait(cvar);
pthread_mutex_unlock(mutex);

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

Однако возможны две проблемы с производительностью:

  • "Поторопись и подожди". В принципе, если вы сигнализируете, что блокировка удерживается, другой поток все равно должен ждать, пока мьютексы будут доступны. Многие реализации pthreads вместо пробуждения другого потока просто переносят его в очередь ожидания мьютекса, сохраняя ненужный цикл ожидания → wait. В некоторых случаях, однако, это не реализуется или недоступно, что приводит к потенциальному ложному переключателю контекста или IPI.
  • Поддельные пробуждения. Если вы сигнализируете после разблокировки, возможно, что другой поток выдает еще одно пробуждение. Рассмотрим следующий сценарий:

    • В потоке A начинается ожидание добавления элементов в очередь потоков.
    • Thread B вставляет элемент в очередь. После разблокировки очереди, но прежде чем он выдает сигнал, возникает контекстный переключатель.
    • Thread C вставляет элемент в очередь и выдает сигнал cvar.
    • Thread A просыпается и обрабатывает оба элемента. Затем он возвращается к ожиданию очереди.
    • Thread B возобновляет и сигнализирует cvar.
    • Thread A просыпается, а затем сразу возвращается к сну, потому что очередь пуста.

    Как вы можете видеть, это может привести к ложному пробуждению, что может привести к отмене некоторого времени процессора.

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

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

Ответ 2

Ответ на ваш вопрос: "Да". На самом деле, это немного предпочтительнее (как вы, наверное, догадались), поскольку это позволяет избежать проблемы "поспешить и ждать" пробуждения потока, чтобы проверить условие, только чтобы он немедленно блокировал мьютекс, который он должен получить, прежде чем тестировать состояние.

Этот ответ основан на предположении, что эти вещи верны:

  • lock - это тонкая оболочка для pthread_mutex_lock.
  • unLock - это тонкая оболочка для pthread_mutex_unlock.
  • signal - это обертка для pthread_cond_signal.
  • Мьютекс: ваша блокировка и разблокировка - это то, что вы даете pthread_cond_wait.

Ответ 3

Эта статья действительно стоит прочитать на ваш вопрос:

Сигнал с мьютексом или нет?

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

  • ждать сигнала (условный var), удерживая мьютекс. В результате потоки присоединяются к условной очереди var, а затем переходят в режим сна.
  • но без мьютекса. В этом случае поток не будет спать, но блокирует его. (Ошибка, которую я сделал по этому поводу, заключается в том, что я думал, что она тоже будет спать. В этом случае, если сигналы производителя и контекстный переключатель произойдут до того, как он выпустит мьютекс, тогда все потоки проснутся и узнают, что они не могут заблокировать мьютексы, пойти спать вечно. Это неправильно, потому что они не будут спать, но ждать и блокировать).

Pthreads реализованы с помощью wait-morphing, то есть вместо того, чтобы просыпать потоки при передаче сигналов, он просто переносит потоки условной переменной на присоединенную очередь mutex. Таким образом, сигнал при блокировке является более предпочтительным без значительного воздействия на производительность.

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