Вопрос с собеседования · Основы

Чем мьютекс отличается от семафора?

JuniorКонкурентность
Ещё спрашивают так:
  • Что такое монитор?
  • Зачем нужна условная переменная?
  • Что такое read-write lock?
Коротко

Мьютекс — замок на одного: в критической секции один поток, отпустить может только владелец. Семафор — счётчик разрешений: одновременно до N потоков.

  • Семафор на 1 похож на мьютекс, но у него нет владельца
  • Семафор ограничивает параллелизм: не больше 10 запросов к внешнему API
  • Монитор = мьютекс + условные переменные (wait/notify, synchronized в Java)

А ещё Read-write lock: много читателей или один писатель

Подробно

Мьютекс (mutual exclusion) обеспечивает взаимное исключение: захватил — работаешь, остальные ждут; отпустил — один из ждущих заходит. У мьютекса есть владелец: отпустить его должен тот же поток. Бывают реентерабельные — поток может захватить свой же замок повторно (synchronized и ReentrantLock в Java, RLock в Python), иначе рекурсивный вызов заблокирует сам себя.

Семафор — счётчик. acquire уменьшает его и ждёт, если счётчик ноль; release увеличивает и будит ждущего. Владельца нет: освободить может любой поток. Применения:

  • ограничение параллелизма — не больше N одновременных запросов к базе или внешнему API, N соединений в пуле;
  • сигнализация — один поток делает release, другой ждёт на acquire (семафор с нулём);
  • двоичный семафор (N = 1) похож на мьютекс, но из-за отсутствия владельца легко ошибиться.

Монитор — объект, в котором мьютекс объединён с условными переменными: поток в критической секции может ждать условия (wait), временно отпуская замок, а другой — разбудить его (notify). В Java каждый объект — монитор: synchronized + wait/notify; современнее — ReentrantLock + Condition. Ждать условия надо в цикле while (!условие) wait() — бывают ложные пробуждения, и пока поток просыпался, условие мог перехватить другой.

Read-write lock — много читателей одновременно или один писатель (ReentrantReadWriteLock). Выгоден, когда чтений намного больше и критическая секция заметная; иначе накладные расходы съедают выигрыш. StampedLock добавляет оптимистичное чтение без блокировки.

Цена. Свободный замок захватывается атомарной инструкцией за десятки наносекунд. Занятый — поток засыпает и просыпается через ядро (переключение контекста), это микросекунды. Поэтому критические секции делают короткими и не делают внутри них ввод-вывод.

Пример

import threading, time

api_slots = threading.Semaphore(3)      # не больше 3 одновременных вызовов

def call_api(i):
    with api_slots:
        print("старт", i)
        time.sleep(1)                   # одновременно спят максимум трое

for i in range(9):
    threading.Thread(target=call_api, args=(i,)).start()

Что хотят услышать

  • Мьютекс — один поток и владелец, семафор — счётчик без владельца.
  • Применение семафора для ограничения параллелизма.
  • Монитор и условные переменные, ожидание в цикле.
  • Read-write lock и когда он оправдан — плюс.

Частые ошибки

  • Говорить, что семафор — «мьютекс на несколько потоков» и не упоминать отсутствие владельца.
  • Вызывать wait через if, а не в цикле.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Мьютекс пускает до N потоков одновременно, семафор — только одного; отпустить мьютекс может любой поток, а семафор — только владелец
  • ✗Отпустить мьютекс может любой поток
  • ✗Семафор нельзя использовать для ограничения параллелизма
  • ✓Мьютекс — один поток в критической секции, отпускает владелец; семафор — счётчик, пускает до N потоков

Что спросят дальше

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования