← Назад к вопросам
Что такое вытесняющая многозадачность?
1.8 Middle🔥 81 комментариев
#Python Core#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Вытесняющая многозадачность (Preemptive Multitasking)
Определение
Вытесняющая многозадачность — это метод управления процессами, при котором операционная система сама решает, когда переключить выполнение с одного процесса (или потока) на другой. Процесс не может сам удерживать процессор дольше выделенного ему временного интервала.
Как это работает
# Пример с потоками (демонстрация концепции)
import threading
import time
def task(name):
for i in range(3):
print(f"{name}: итерация {i}")
# Операционная система может вытеснить этот поток
# в ЛЮБОЙ момент, не только при вызове sleep()
time.sleep(0.1)
thread1 = threading.Thread(target=task, args=("Поток 1",))
thread2 = threading.Thread(target=task, args=("Поток 2",))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
# Вывод будет перемешанным, так как ОС переключает потоки
# Поток 1: итерация 0
# Поток 2: итерация 0
# Поток 1: итерация 1
# Поток 2: итерация 1
# ... и так далее
Вытесняющая vs Кооперативная многозадачность
# КООПЕРАТИВНАЯ многозадачность (old way)
# Процесс должен САМ отдать контроль
def coop_task_1():
print("Задача 1: начало")
yield # Явное отдание контроля
print("Задача 1: конец")
def coop_task_2():
print("Задача 2: начало")
yield # Явное отдание контроля
print("Задача 2: конец")
# Вызов в определённом порядке
gen1 = coop_task_1()
gen2 = coop_task_2()
for _ in range(2):
next(gen1)
next(gen2)
# Вывод ВСЕГДА:
# Задача 1: начало
# Задача 2: начало
# Задача 1: конец
# Задача 2: конец
Вытесняющая многозадачность в Python
import threading
import random
from time import sleep
def preemptive_task(name):
"""ОС может прервать эту функцию в ЛЮБОЙ момент"""
for i in range(3):
print(f"{name}: начало итерации {i}")
# Операционная система может вытеснить поток
# даже ВНУТРИ простого цикла
for _ in range(1000000):
pass # ОС переключит поток где-то здесь
print(f"{name}: конец итерации {i}")
threads = [
threading.Thread(target=preemptive_task, args=(f"T{i}",))
for i in range(3)
]
for t in threads:
t.start()
for t in threads:
t.join()
# Вывод нередерминирован! Потоки переключаются ОС
Временные срезы (Time Slicing)
import threading
import time
from threading import Lock
# Демонстрация временных срезов
lock = Lock()
shared_counter = 0
def increment_counter(thread_id, iterations):
global shared_counter
for i in range(iterations):
# ОС может вытеснить поток даже в важном коде
local_value = shared_counter # Получили значение
# ВОТ ЗДЕСЬ ОС может переключить на другой поток!
time.sleep(0) # Даёт шанс переключиться
shared_counter = local_value + 1 # Увеличили
# Race condition!
thread1 = threading.Thread(target=increment_counter, args=(1, 1000))
thread2 = threading.Thread(target=increment_counter, args=(2, 1000))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"Счётчик: {shared_counter}")
# Результат: не 2000, а меньше! Например, 1500 или 1800
# Потому что потоки вытеснялись в неправильное время
Синхронизация при вытесняющей многозадачности
import threading
from threading import Lock
# Правильный способ — использовать блокировки
lock = Lock()
shared_counter = 0
def safe_increment(thread_id, iterations):
global shared_counter
for i in range(iterations):
with lock: # ОС НЕ может вытеснить поток внутри lock
shared_counter += 1
# Атомарная операция
thread1 = threading.Thread(target=safe_increment, args=(1, 1000))
thread2 = threading.Thread(target=safe_increment, args=(2, 1000))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"Счётчик: {shared_counter}") # Всегда 2000!
GIL и вытесняющая многозадачность в Python
import threading
# Python использует Global Interpreter Lock (GIL)
# Это означает, что даже при вытесняющей многозадачности
# только один поток может выполнять Python код одновременно
def cpu_bound_task():
total = 0
for i in range(100000000):
total += i
print(f"Результат: {total}")
# На многоядерной системе это НЕ будет быстрее!
thread1 = threading.Thread(target=cpu_bound_task)
thread2 = threading.Thread(target=cpu_bound_task)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
# GIL вытесняет потоки, но только один выполняется в CPU
# Для CPU-bound задач используй multiprocessing, не threading
Процессы vs потоки в вытесняющей многозадачности
from multiprocessing import Process
import threading
import time
# Потоки: вытесняющая многозадачность, общая память
def thread_task():
print("Поток выполняется")
t = threading.Thread(target=thread_task)
t.start() # ОС вытеснит этот поток
# Процессы: вытесняющая многозадачность, отдельная память
def process_task():
print("Процесс выполняется")
p = Process(target=process_task)
p.start() # ОС вытеснит этот процесс
t.join()
p.join()
Проблемы вытесняющей многозадачности
import threading
# Проблема 1: Race condition
balance = 100
def withdraw(amount):
global balance
if balance >= amount: # ОС вытеснит здесь!
balance -= amount
def deposit(amount):
global balance
balance += amount
# Проблема 2: Deadlock
lock1 = threading.Lock()
lock2 = threading.Lock()
def func_a():
with lock1:
time.sleep(0.1)
with lock2: # Может ждать lock2
pass
def func_b():
with lock2:
time.sleep(0.1)
with lock1: # Может ждать lock1
pass
# Проблема 3: Priority inversion
# Высокоприоритетный поток ждёт низкоприоритетного
Преимущества вытесняющей многозадачности
- Ответственность ОС — не нужно вручную отдавать контроль
- Справедливость — все процессы получают время CPU
- Безопасность — один сломанный процесс не зависнет систему
- I/O операции — пока один поток ждёт диска, другой может работать
Итоговое резюме
- Вытесняющая многозадачность — ОС сама решает, когда переключить задачи
- Time slicing — каждой задаче выделяется временной интервал
- Race conditions — опасность при доступе к общей памяти
- Синхронизация — используй Lock, Semaphore, Queue
- Python + GIL — потоки не дают параллелизм для CPU-bound, только для I/O
- Используй threading для I/O-bound и multiprocessing для CPU-bound