На теоретическом уровне они семантически не отличаются. Вы можете реализовать мьютекс, используя семафоры или наоборот (см. здесь для примера). На практике реализация отличается, и они предлагают несколько разные услуги.
Практическое различие (с точки зрения окружающих их системных служб) состоит в том, что реализация мьютекса направлена на то, чтобы стать более легким механизмом синхронизации. Говоря оракулом, мьютексы известны как защелки , а семафоры известны как ждет .
На самом низком уровне они используют какой-то атомарный тест и устанавливают механизм. Это считывает текущее значение ячейки памяти, вычисляет некоторый вид условия и записывает значение в этом месте в одной инструкции, которую нельзя прервать . Это означает, что вы можете приобрести мьютекс и проверить, есть ли у вас кто-нибудь еще до вас.
Типичная реализация мьютекса имеет процесс или поток, выполняющий инструкцию test-and-set и оценивающий, установил ли мьютекс что-либо еще. Ключевым моментом здесь является отсутствие взаимодействия с планировщиком , поэтому мы понятия не имеем (и нам все равно), кто установил блокировку. Затем мы либо оставляем наш временной интервал и повторяем попытку при повторном планировании задачи, либо выполняем spin-lock . Спин-блокировка - это алгоритм вроде:
Count down from 5000:
i. Execute the test-and-set instruction
ii. If the mutex is clear, we have acquired it in the previous instruction
so we can exit the loop
iii. When we get to zero, give up our time slice.
Когда мы закончим выполнение нашего защищенного кода (известного как критическая секция ), мы просто устанавливаем значение мьютекса на ноль или что-либо еще, означающее «очистить». Если несколько задач пытаются получить мьютекс, то следующая задача, запланированная после освобождения мьютекса, получит доступ к ресурсу. Обычно вы используете мьютексы для управления синхронизированным ресурсом, где эксклюзивный доступ необходим только в течение очень коротких периодов времени, обычно для обновления общей структуры данных.
Семафор - это синхронизированная структура данных (обычно с использованием мьютекса), которая имеет счетчик и некоторые оболочки системных вызовов, которые взаимодействуют с планировщиком чуть глубже, чем библиотеки мьютекса. Семафоры увеличиваются и уменьшаются и используются для блокировки задач, пока что-то еще не будет готово. См. Проблема производителя / потребителя для простого примера этого. Семафоры инициализируются до некоторого значения - двоичный семафор - это просто особый случай, когда семафор инициализируется равным 1. Публикация на семафор приводит к пробуждению процесса ожидания.
Основной алгоритм семафора выглядит так:
(somewhere in the program startup)
Initialise the semaphore to its start-up value.
Acquiring a semaphore
i. (synchronised) Attempt to decrement the semaphore value
ii. If the value would be less than zero, put the task on the tail of the list of tasks waiting on the semaphore and give up the time slice.
Posting a semaphore
i. (synchronised) Increment the semaphore value
ii. If the value is greater or equal to the amount requested in the post at the front of the queue, take that task off the queue and make it runnable.
iii. Repeat (ii) for all tasks until the posted value is exhausted or there are no more tasks waiting.
В случае двоичного семафора основное практическое различие между ними заключается в характере системных служб, окружающих фактическую структуру данных.
РЕДАКТИРОВАТЬ: Как справедливо заметил Эван, спин-блокировки будут замедлять однопроцессорную машину. Вы должны использовать спин-блокировку только на многопроцессорном блоке, потому что на одном процессоре процесс, удерживающий мьютекс, никогда не сбросит его, пока выполняется другая задача. Спин-блокировки полезны только на многопроцессорных архитектурах.