← Назад к вопросам

Как ОС работает с потоками?

2.3 Middle🔥 201 комментариев
#DevOps и инфраструктура#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как ОС работает с потоками

Потоки — это основной механизм параллельного выполнения кода на современных системах. ОС управляет ими через специальные структуры данных и алгоритмы планирования.

Архитектура потоков в ОС

Каждый поток имеет собственный:

  • 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). Когда квант истечёт, ОС:

  1. Сохраняет состояние текущего потока
  2. Выбирает следующий поток из очереди
  3. Загружает его состояние в процессор

Context Switch (переключение контекста)

Это дорогая операция:

импортируйте time
start = time.perf_counter()
# Context switch примерно 1-10 микросекунд
# На современных системах это 1000+ тактов CPU

При переключении:

  1. Все регистры CPU сохраняются в kernel stack
  2. TLB (Translation Lookaside Buffer) очищается
  3. Cache инвалидируется
  4. Новый контекст загружается из памяти

Приоритеты потоков

ОС использует различные приоритеты:

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)

Лучшие практики

  1. Для CPU-bound операций: используй multiprocessing (разные процессы)
  2. Для I/O-bound операций: используй threading (один процесс, отпускается GIL)
  3. Для современного асинхрона: используй 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.