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

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

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

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

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

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

6 ответов


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

Thread1:

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

Thread2:

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

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

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

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

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


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

lock(mutex)

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

unlock(mutex)

do_something_on_flag_set();

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


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


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

  1. блокировать mutex

  2. Подождите на условном var


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

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

Читайте также Расширенное Программирование Unix и Расширенный Linux Программирование

П. С. параллельность и темы такие сложные понятия, чтобы понять. Потратьте время на чтение, эксперименты и повторное чтение.


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

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

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

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

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


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

Thread1:

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
}

Thread2:

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

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