Вступление
Каждый программист, который интересовался параллельным программированием(а оно становиться необходимым в современном мире), слышал про проблему совместного использования ресурса несколькими потоками. Это приводит к взаимным блокировкам, data race и другим проблемам, которые либо замедляют, либо ломают нашу программу. Чтобы избежать всего этого и были придуманы примитивы синхронизации. Два из них мы сегодня разберём.
Семафор
Семафор своё название получил из-за того, что управляет потоками. Указывает кому можно войти в синхронизированную область, а кому нельзя.
Используется в том случае, когда нужно дать доступ к ресурсу нескольким потокам. Например, нам нужно сделать так, чтобы наш сервер мог обрабатывать только 10 соединений, а остальные закрывал.
Как это работает?
Внутри семафора есть переменная, которая отвечает за количество потоков, которые могут зайти в синхронизированную область. Обычно задаётся при инициализации. Важно, чтобы эта переменная была атомарной, то есть, даже если несколько потоков с ней работают, изменит её только один в один момент времени.
Чтобы скрыть существование переменной, обычно используются методы wait() и signal(). Перед тем, как получить доступ к ресурсу, программист в коде вызывает wait(), тем самым неявно уменьшает переменную, отвечающую за количество потоков, имеющих доступ. Если она равна нулю, то поток ждёт, когда кто-то другой не освободит ресурс, то есть вызовет signal(), тем самым увеличив переменную.
Стоит отметить, ресурс не принадлежит потоку, который получил к нему доступ, то есть вызвать signal() может совершенно другой поток, который не зашёл или зашёл в синхронизированный блок, тем самым освободить место для доступа.
Мьютекс
Во многих ресурсах пишут, что мьютекс - это одноместный семафор, который либо занят, либо свободен, то есть атомарная переменная, отвечающая за количество потоков, при инициализации равна нулю.
Отчасти это правда. Ведь действительно, мьютекс может быть либо занят либо свободен и под капотом у него одноместный семафор за тем только отличием, что вызвать метод signal() может только тот поток, который получил доступ к ресурсу. Можно сказать, что поток получил ресурс к себе в собственность и никто не может без разрешения отнять его, поэтому важно не забывать в коде отдавать ресурс из единоличного пользования потока всем остальным. Этим он и отличается от семафора - семантикой принадлежности ресурса.
Итог
Мьютекс и семафор очень похожи. Даже можно сказать, что мьютекс это частный случай семафора за тем исключением, что при использовании первого ресурс переходит к нему в собственность, а при использовании второго такого не происходит и освободить ресурс может и другой поток, но бездумно этого делать не стоит, потому что тогда доступ к ресурсу будет у бОльшего количества потоков.