Процессы и потоки в ОС
Чем процесс отличается от потока, состояния процесса, планирование, взаимоблокировки, мьютексы и семафоры — база лабораторных по ОС.
Процесс и поток
| Признак | Процесс | Поток |
|---|---|---|
| Адресное пространство | Собственное, изолированное | Общее с другими потоками процесса |
| Создание | Дорогое | Дешёвое |
| Обмен данными | Через IPC: каналы, разделяемая память, сокеты | Через общие переменные |
| Падение | Не влияет на другие процессы | Роняет весь процесс |
| Переключение контекста | Медленное (смена таблиц страниц) | Быстрое |
Формулировка для защиты: процесс — это единица владения ресурсами, поток — единица планирования и выполнения.
Состояния процесса
- Новый — создаётся, ресурсы ещё не выделены.
- Готовый — всё есть, ждёт процессор.
- Выполняющийся — занимает процессор.
- Ожидающий (блокированный) — ждёт ввод-вывод или событие.
- Завершённый — работа окончена, освобождаются ресурсы.
Ключевой переход: из состояния «ожидающий» процесс попадает не сразу на выполнение, а в очередь готовых. Это стандартная ловушка в тестах.
Алгоритмы планирования
| Алгоритм | Суть | Недостаток |
|---|---|---|
| FCFS | Первым пришёл — первым обслужен | Эффект конвоя: длинный процесс держит всех |
| SJF | Кратчайший первым | Нужно знать время заранее; голодание длинных |
| Round Robin | Каждому квант времени по кругу | Чувствителен к размеру кванта |
| Приоритетное | По приоритетам | Голодание низкоприоритетных (лечится старением) |
| Многоуровневые очереди | Разные классы задач в разных очередях | Сложность настройки |
Проблема синхронизации
Состояние гонки (race condition) возникает, когда два потока одновременно меняют общие данные и результат зависит от порядка выполнения. Классический пример — счётчик: операция i++ не атомарна, она состоит из чтения, инкремента и записи.
import threading
counter = 0
lock = threading.Lock()
def worker():
global counter
for _ in range(100_000):
with lock: # критическая секция
counter += 1
ts = [threading.Thread(target=worker) for _ in range(4)]
for t in ts: t.start()
for t in ts: t.join()
print(counter) # без lock результат каждый раз разный
Средства синхронизации
- Мьютекс — двоичный замок, владелец один; освобождать должен тот, кто захватил.
- Семафор — счётчик, разрешает доступ N потокам одновременно (например, пул из 3 соединений).
- Условная переменная — ожидание события с последующим пробуждением.
- Монитор — высокоуровневая конструкция языка, объединяющая мьютекс и условные переменные (synchronized в Java, lock в C#).
Взаимоблокировка (deadlock)
Возникает при одновременном выполнении четырёх условий Коффмана: взаимное исключение, удержание с ожиданием, отсутствие вытеснения, циклическое ожидание. Чтобы не было deadlock, достаточно исключить любое из них.
- Предотвращение: захватывать ресурсы всегда в одном и том же порядке — разрывает циклическое ожидание.
- Избежание: алгоритм банкира, выдающий ресурсы только в безопасное состояние.
- Обнаружение и восстановление: строить граф ожидания, при цикле снимать один из процессов.
- Игнорирование («страусиный алгоритм») — то, что делают большинство ОС общего назначения.
Частые вопросы
Что такое голодание и чем оно отличается от deadlock?
При deadlock процессы блокируют друг друга навсегда. При голодании процесс в принципе может выполниться, но его постоянно обходят более приоритетные. Лечится старением приоритета.
Зачем нужны процессы, если потоки дешевле?
Изоляция. Падение или уязвимость в одном процессе не затрагивает остальные — поэтому браузеры запускают вкладки отдельными процессами.