Когда следует использовать семафор и когда следует использовать условную переменную (CondVar)?
Блокировки используются для взаимного исключения. Если вы хотите, чтобы фрагмент кода был атомарным, поставьте вокруг него блокировку. Теоретически для этого можно использовать двоичный семафор, но это особый случай.
Семафоры и условные переменные строятся на основе взаимного исключения, обеспечиваемого блокировками, и используются для обеспечения синхронизированного доступа к совместно используемым ресурсам. Их можно использовать для аналогичных целей.
Условная переменная обычно используется, чтобы избежать ожидания занятости (повторяющегося цикла при проверке условия) во время ожидания доступности ресурса. Например, если у вас есть поток (или несколько потоков), который не может продолжать работу до тех пор, пока очередь не станет пустой, подход с ожиданием занятости будет заключаться в том, чтобы просто сделать что-то вроде:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
Проблема в том, что вы тратите впустую процессорное время, заставляя этот поток повторно проверять условие. Почему бы вместо этого не иметь переменную синхронизации, которая может сигнализировать потоку, что ресурс доступен?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Предположительно, у вас будет поток где-то еще, который вытаскивает вещи из очереди. Когда очередь пуста, он может вызвать syncVar.signal ()
, чтобы разбудить случайный поток, который спит на syncVar.wait ()
(или обычно есть также метод signalAll ()
или broadcast ()
для пробуждения всех ожидающих потоков).
Я обычно использую такие переменные синхронизации, когда у меня есть один или несколько потоков, ожидающих одного конкретного условия (например, чтобы очередь была пустой).
Семафоры можно использовать аналогично, но я думаю, что их лучше использовать, когда у вас есть общий ресурс, который может быть доступен или недоступен на основе некоторого целого числа доступных вещей. Семафоры хороши для ситуаций производитель / потребитель, когда производители распределяют ресурсы, а потребители их потребляют.
Подумайте, был ли у вас автомат по продаже газировки. Есть только один автомат с газировкой, и это общий ресурс. У вас есть один поток, который является поставщиком (производителем), который отвечает за хранение машины, и N потоков, которые являются покупателями (потребителями), которые хотят получать газированные напитки из машины. Количество содовой в машине - это целое число, которое будет управлять нашим семафором.
Каждый поток покупателя (потребителя), который приходит к автомату с газировкой, вызывает метод семафора down ()
, чтобы взять газировку. Это приведет к получению содовой из машины и уменьшению количества доступных газированных напитков на 1. Если есть доступные газированные напитки, код просто продолжит без проблем проходить мимо оператора down ()
. Если газированные напитки недоступны, поток будет спать здесь, ожидая уведомления о том, когда газированные напитки снова станут доступны (когда в машине будет больше газированных напитков).
Поток поставщика (производителя), по существу, будет ждать, пока автомат с газировкой не станет пустым.Продавец получает уведомление, когда из автомата берется последняя газировка (и один или несколько потребителей потенциально ждут, чтобы достать газировку). Поставщик будет пополнять запасы газированной воды с помощью метода семафор up ()
, доступное количество газированных напитков будет увеличиваться каждый раз, и, таким образом, ожидающие потоки потребителей будут получать уведомление о том, что доступно больше содовой.
Методы wait ()
и signal ()
переменной синхронизации обычно скрыты внутри down ()
и up ()
операции семафора.
Конечно, эти два варианта частично совпадают. Существует множество сценариев, в которых семафор или условная переменная (или набор условных переменных) могут служить вашим целям. И семафоры, и переменные условия связаны с объектом блокировки, который они используют для поддержания взаимного исключения, но затем они предоставляют дополнительные функции поверх блокировки для синхронизации выполнения потока. В основном вам решать, какой из них наиболее подходит для вашей ситуации.
Это не обязательно самое техническое описание, но именно так оно имеет смысл в моей голове.
Семафоры могут использоваться для реализации монопольного доступа к переменные, однако они предназначены для синхронизации. С другой стороны, мьютексы имеют семантику, которая строго связана с взаимным исключением: только процесс, заблокировавший ресурс, может разблокировать его.
К сожалению, вы не можете реализовать синхронизацию с мьютексами, поэтому у нас есть условные переменные. Также обратите внимание, что с помощью переменных условия вы можете разблокировать все ожидающие потоки в один и тот же момент, используя разблокировку широковещательной рассылки. Этого нельзя сделать с семафорами.
Я записываю переменные состояния при синхронизации монитора. Я обычно видел семафоры и мониторы как два разных стиля синхронизации. Между ними есть различия в том, сколько данных состояния хранится по своей сути и как вы хотите моделировать код, но на самом деле нет никаких проблем, которые можно было бы решить одним, но не другим.
Я предпочитаю кодировать форму монитора; на большинстве языков, с которыми я работаю, все сводится к мьютексам, условным переменным и некоторым вспомогательным переменным состояния. Но семафоры тоже подойдут.