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

Когда требуется переменная условия, недостаточно ли это мьютекса?

Я уверен, что мьютекса недостаточно, чтобы существовала концепция условных переменных; но он бьет меня, и я не могу убедить себя в конкретном сценарии, когда важна переменная состояния.

Различия между условными переменными, мьютексами и блокировками принятый ответ говорит, что переменная условия - это

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

Если я запутался в том, что поток может также ждать мьютекса, и когда он получает сигнал, просто означает, что теперь доступна переменная, зачем мне нужна переменная условия?

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

4b9b3361

Ответ 1

Даже если вы можете использовать их так, как вы описываете, мьютексы не были предназначены для использования в качестве механизма уведомления/синхронизации. Они предназначены для обеспечения взаимоисключающего доступа к общему ресурсу. Использование мьютексов для сигнализации о состоянии неудобно, и я предполагаю, что будет выглядеть примерно так (где Thread1 сигнализируется Thread2):

Резьба1:

while(1) {
    lock(mutex); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex); // Tells Thread2 we are done
}

Резьба2:

while(1) {
    ... // do the work that precedes notification
    unlock(mutex); // unblocks Thread1
    lock(mutex); // lock the mutex so Thread1 will block again
}

Есть несколько проблем с этим:

  • Thread2 не может продолжать "выполнять работу, предшествующую уведомлению", пока Thread1 не закончит работу "после уведомления". С этим дизайном Thread2 даже не нужен, то есть почему бы не переместить "работу, которая предшествует" и "работать после уведомления", в тот же поток, поскольку только один может запускаться в заданное время!
  • Если Thread2 не может превзойти Thread1, Thread1 немедленно переблокирует мьютекс, когда он повторяет цикл while (1), и Thread1 будет продолжать выполнять "работу после уведомления", даже несмотря на отсутствие уведомления. Это означает, что вы должны как-то гарантировать, что Thread2 заблокирует мьютекс до Thread1. Как ты это делаешь? Может быть, заставлять событие расписания спать или каким-то другим конкретным ОС, но даже это не гарантирует работу в зависимости от времени, вашей ОС и алгоритма планирования.

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

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

Ответ 2

Mutex предназначен для эксклюзивного доступа к совместно используемому ресурсу, а условная переменная - для ожидания того, что условие будет истинным. Люди могут подумать, что они могут реализовать условную переменную без поддержки ядра. Общим решением, которое может возникнуть, является "флаг + мьютекс":

lock(mutex)

while (!flag) {
    sleep(100);
}

unlock(mutex)

do_something_on_flag_set();

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

Ответ 3

Я тоже думал об этом, и самая важная информация, которой я отсутствовала повсюду, заключалась в том, что мьютекс может владеть (и изменять) только в один поток. Поэтому, если у вас есть один производитель и больше потребителей, продюсеру придется ждать на мьютексе для производства. С cond. которую он может произвести в любое время.

Ответ 4

Условные пары var и mutex могут быть заменены двоичной семафором и парой mutex. Последовательность операций потребительского потока при использовании условного var + mutex:

  • Блокировка мьютекса

  • Дождитесь условного var

  • Процесс

  • Отменить мьютексы

Последовательность операций производственного потока

  • Блокировка мьютекса

  • Сигнал условного var

  • Отменить мьютексы

Соответствующая последовательность потребительских потоков при использовании пары sema + mutex

  • Подождите, пока в двоичной сема

  • Блокировка мьютекса

  • Проверить ожидаемое условие

  • Если условие истинно, обработайте.

  • Отменить мьютексы

  • Если проверка состояния на шаге 3 была ложной, вернитесь к шагу 1.

Последовательность для потока производителя:

  • Блокировка мьютекса

  • Опубликовать двоичную сема

  • Отменить мьютексы

Как вы можете видеть, безусловная обработка на шаге 3 при использовании условного var заменяется условной обработкой на шаге 3 и шаге 4 при использовании двоичной сема.

Причина в том, что при использовании sema + mutex в состоянии гонки другой потребительский поток может пробираться между шагами 1 и 2 и обрабатывать/аннулировать условие. Этого не произойдет при использовании условного var. При использовании условного var условие должно быть истинным после шага 2.

Двоичный семафор можно заменить обычным семафором подсчета. Это может привести к тому, что шаг 6 до шага 1 будет выполняться несколько раз.

Ответ 5

Вам нужны переменные условия, которые будут использоваться с мьютексом (каждый cond.var. принадлежит мьютексу) для передачи изменяющихся состояний (условий) из одного потока в другой. Идея состоит в том, что поток может ждать, пока какое-то условие станет истинным. Такие условия специфичны для программы (т.е. "Очередь пуста", "матрица большая", "некоторый ресурс почти исчерпан", "завершен какой-то этап вычисления" и т.д.). Мьютекс может иметь несколько связанных переменных условия. И вам нужны переменные условия, потому что такие условия не всегда могут быть выражены просто так: "мьютекс заблокирован" (поэтому вам нужно транслировать изменения условий в другие потоки).

Прочитайте несколько хороших учебников по теме поси, например. этот учебник или который или который один. Еще лучше, прочитайте хорошую книгу pthread. См. этот вопрос.

Также читайте Расширенное программирование Unix и Расширенное программирование Linux

P.S. Parallelism и потоки - это сложные концепции, которые нужно понять. Найдите время, чтобы прочитать, поэкспериментировать и прочитать снова.

Ответ 6

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

Как упоминалось в http://en.cppreference.com/w/cpp/thread/mutex/unlock,

Мьютекс должен быть заблокирован текущим потоком выполнения, в противном случае поведение undefined.

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

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


Например, вы можете исправить случай @slowjelj с дополнительным мьютексом (это может быть неправильное исправление):

Резьба1:

lock(mutex0);
while(1) {
    lock(mutex0); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex1); // Tells Thread2 we are done
}

Резьба2:

while(1) {
    lock(mutex1); // lock the mutex so Thread1 will block again
    ... // do the work that precedes notification
    unlock(mutex0); // unblocks Thread1
}

Но ваша программа будет жаловаться на то, что вы вызвали утверждение, оставленное компилятором (например, "разблокировка незанятых мьютексов" в Visual Studio 2015).