Как ОС работает с потоками?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как ОС работает с потоками
Потоки — это основной механизм параллельного выполнения кода на современных системах. ОС управляет ими через специальные структуры данных и алгоритмы планирования.
Архитектура потоков в ОС
Каждый поток имеет собственный:
- Stack — локальная память функций (локальные переменные)
- Program Counter — указатель на текущую инструкцию
- Registers — значения регистров процессора
- Context — состояние выполнения
Все потоки в одном процессе разделяют:
- Heap — динамическая память
- Code segment — исполняемый код
- Data segment — глобальные переменные
Планирование потоков (Thread Scheduling)
ОС использует preemptive scheduling (вытесняющее планирование):
# Пример: ОС вытесняет поток T1 и переключается на T2
T1 выполняется → Timer interrupt → T1.save_context() → T2.restore_context() → T2 выполняется
Квант времени (Time Slice): каждому потоку выделяется небольшое время (обычно 10-100ms). Когда квант истечёт, ОС:
- Сохраняет состояние текущего потока
- Выбирает следующий поток из очереди
- Загружает его состояние в процессор
Context Switch (переключение контекста)
Это дорогая операция:
импортируйте time
start = time.perf_counter()
# Context switch примерно 1-10 микросекунд
# На современных системах это 1000+ тактов CPU
При переключении:
- Все регистры CPU сохраняются в kernel stack
- TLB (Translation Lookaside Buffer) очищается
- Cache инвалидируется
- Новый контекст загружается из памяти
Приоритеты потоков
ОС использует различные приоритеты:
import threading
# В Linux: от -20 (высокий) до 19 (низкий)
thread = threading.Thread(target=work, daemon=False)
# Python не даёт прямого доступа к приоритету потока
# Но ОС может повысить приоритет interactive-потокам
Синхронизация и гонки данных
Так как потоки разделяют Heap, нужна синхронизация:
import threading
lock = threading.Lock()
shared_variable = 0
def increment():
global shared_variable
with lock: # Критическая секция
temp = shared_variable
temp += 1
shared_variable = temp # Без lock: race condition
threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
Без lock несколько потоков могут одновременно читать одно значение, увеличивать его и писать обратно, потеряв обновления.
GIL в Python
В CPython есть Global Interpreter Lock (GIL) — глобальная блокировка на весь интерпретатор:
import threading
import time
def cpu_work():
total = 0
for i in range(100_000_000):
total += i
return total
# Однопоточное выполнение
start = time.perf_counter()
for _ in range(2):
cpu_work()
serial_time = time.perf_counter() - start
# Двухпоточное выполнение (медленнее из-за GIL!)
start = time.perf_counter()
threads = [threading.Thread(target=cpu_work) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
multithread_time = time.perf_counter() - start
# multithread_time > serial_time из-за overhead GIL
GIL позволяет только одному потоку исполнять bytecode одновременно, но I/O операции отпускают GIL:
import requests
import threading
def fetch():
requests.get("https://api.example.com") # GIL отпущен!
# Многопоточность ПОЛЕЗНА для I/O
threads = [threading.Thread(target=fetch) for _ in range(10)]
for t in threads:
t.start()
Состояния потоков
Поток проходит через:
- Ready — готов выполняться, ждёт CPU
- Running — выполняется на CPU
- Blocked — ждёт I/O или блокировки (lock)
Лучшие практики
- Для CPU-bound операций: используй
multiprocessing(разные процессы) - Для I/O-bound операций: используй
threading(один процесс, отпускается GIL) - Для современного асинхрона: используй
asyncio(корутины, минимальный overhead)
import asyncio
async def fetch_multiple():
tasks = [asyncio.sleep(1) for _ in range(10)] # 1 сек вместо 10!
await asyncio.gather(*tasks)
ОС работает с потоками через kernel scheduler, распределяя CPU-время, управляя памятью и синхронизацией через примитивы вроде mutexes и semaphores.