Чем отличаются мьютекс (Mutex) и семафор (Semaphore)

Ответ

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

Мьютекс (Mutex - Mutual Exclusion)

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

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

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

from threading import Lock, Thread

# В Python мьютекс представлен классом Lock
lock = Lock()
shared_resource = 0

def task():
    global shared_resource
    for _ in range(100000):
        lock.acquire()  # Захват мьютекса
        try:
            shared_resource += 1 # Критическая секция
        finally:
            lock.release() # Освобождение мьютекса

# Запуск потоков...

Семафор (Semaphore)

Назначение: Ограничить доступ к ресурсу определенным количеством потоков.

Принцип работы: Работает как счетчик. При инициализации семафору задается начальное значение (например, 5), которое означает, что 5 потоков могут одновременно получить доступ к ресурсу. Каждый раз, когда поток захватывает семафор, счетчик уменьшается. Когда он освобождает — увеличивается. Если счетчик равен нулю, новые потоки блокируются.

Типы:

  • Счетный семафор (counting semaphore): счетчик > 1.
  • Двоичный семафор (binary semaphore): счетчик равен 1, по поведению очень похож на мьютекс.
from threading import Semaphore, Thread
import time

# Ограничиваем доступ к ресурсу тремя потоками одновременно
semaphore = Semaphore(3)

def access_resource(thread_id):
    print(f"Поток {thread_id} пытается получить доступ...")
    with semaphore: # Автоматический acquire/release
        print(f"Поток {thread_id} получил доступ!")
        time.sleep(2)
        print(f"Поток {thread_id} освобождает ресурс.")

# Запуск 5 потоков, но одновременно работать будут только 3
threads = [Thread(target=access_resource, args=(i,)) for i in range(5)]
for t in threads: t.start()

Сравнительная таблица

Критерий Мьютекс (Mutex) Семафор (Semaphore)
Назначение Взаимное исключение (защита ресурса) Ограничение доступа (контроль параллелизма)
Кол-во потоков Только один N (заданное число)
Владение Принадлежит потоку, который его захватил Не имеет концепции владения
Аналогия Ключ от одной туалетной комнаты Набор из N ключей от одинаковых душевых кабинок

Ответ 18+ 🔞

А, ну вот, опять про эти ваши мьютексы и семафоры. Сидишь такой, пишешь код, всё вроде летает, а потом — бац! — и два потока одновременно лезут в одну переменную, как два мудака в одну дверь. И всё, пиздец, приехали, данные похерились. Вот для этого и придумали всю эту хуйню — синхронизацию.

Так, слушай сюда, разбираем по косточкам.

Мьютекс, он же замок, он же «не лезь, пока я не отпирану»

Представь себе сортир на вокзале. Одна кабинка, один ключ. Это и есть мьютекс, ёпта. Взаимное исключение — звучит умно, а на деле: «Я щас тут сру, вы все снаружи ждите, пока я не выйду и ключ не верну».

Главная фишка — владение. Тот, кто ключ взял, тот его и должен вернуть. Чужой поток не может прийти и сказать: «А ну-ка откройся!» — нихуя. Только хозяин.

Вот смотри, как это в коде выглядит, простейший пример:

from threading import Lock, Thread

lock = Lock()  # Вот он, наш ключ от сортира
shared_resource = 0  # А это наша общая заветная переменная, которую все хотят потрогать

def task():
    global shared_resource
    for _ in range(100000):
        lock.acquire()  # Берём ключ. Если ключа нет — стоишь и тупишь в стену.
        try:
            shared_resource += 1 # Всё, зашли в кабинку, делаем свои делишки.
        finally:
            lock.release() # Выходим, вешаем ключ обратно. Всё, следующий!

Если не использовать lock, то эти 100 тысяч инкрементов из разных потоков превратятся в пиздец и бардак, и в итоге получится не 200 тысяч, а какая-то рандомная хуйня. А так — порядок, один за другим.

Семафор, он же «пустите троих, остальные ждите»

А теперь представь не сортир, а, блядь, душевые в спортзале. Их, допустим, три штуки. Вот семафор — это как раздача ключей от этих душевых. Изначально у тебя на крючке висит три ключа (Semaphore(3)).

Поток приходит, берёт ключ (acquire) — счётчик ключей уменьшается. Пока он моется, другие могут взять оставшиеся два. Но как только ключей не осталось — всё, образовывается очередь, жди, пока кто-то не вернёт ключ (release).

from threading import Semaphore, Thread
import time

# Допустим, у нас есть какой-то ресурс, который может выдержать только 3 подключения одновременно
semaphore = Semaphore(3)

def access_resource(thread_id):
    print(f"Поток {thread_id} подошёл к раздевалке...")
    with semaphore: # Автоматически берём ключ, а потом возвращаем
        print(f"Поток {thread_id} урвал ключ и пошёл мыться!")
        time.sleep(2) # Представь, что тут какая-то полезная работа
        print(f"Поток {thread_id} вышел, ключ на место.")

# Запускаем 5 потоков-грязнуль
threads = [Thread(target=access_resource, args=(i,)) for i in range(5)]
for t in threads: t.start()

Увидишь, что первые три потока сразу зайдут, а четвёртый и пятый будут ждать, пока кто-то не освободит кабинку. Красота, да? Это и есть ограничение параллелизма, а не просто запрет.

Так в чём, блядь, разница-то? Сравниваем

Критерий Мьютекс (Замок от одной кабинки) Семафор (Набор ключей от душевых)
Суть «Никого, кроме меня!» «Пустите N человек!»
Кто внутри Один поток, и всё. N потоков (сколько ключей, столько и людей).
Чей ключ? Только тот, кто взял, может вернуть. Владение. А похуй, кто взял. Может один взять, а другой вернуть (но так делать — идиотизм).
Аналогия Один туалет на всех. Три душевые кабинки на очередь.

Короче, если тебе нужно, чтобы к куску памяти или коду в один момент времени прикасалась только одна сущность — бери мьютекс. Если у тебя есть, условно, пул соединений к базе данных на 10 штук и ты хочешь, чтобы не больше 10 потоков одновременно их использовали, а остальные ждали — вот тут твой выход, семафор.

Вот и вся магия. Не такой уж и ёперный театр, правда? Главное — понять, что тебе нужно: полная изоляция или просто регулировка потока.