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

Что такое вытесняющая многозадачность?

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
# Высокоприоритетный поток ждёт низкоприоритетного

Преимущества вытесняющей многозадачности

  1. Ответственность ОС — не нужно вручную отдавать контроль
  2. Справедливость — все процессы получают время CPU
  3. Безопасность — один сломанный процесс не зависнет систему
  4. I/O операции — пока один поток ждёт диска, другой может работать

Итоговое резюме

  • Вытесняющая многозадачность — ОС сама решает, когда переключить задачи
  • Time slicing — каждой задаче выделяется временной интервал
  • Race conditions — опасность при доступе к общей памяти
  • Синхронизация — используй Lock, Semaphore, Queue
  • Python + GIL — потоки не дают параллелизм для CPU-bound, только для I/O
  • Используй threading для I/O-bound и multiprocessing для CPU-bound
Что такое вытесняющая многозадачность? | PrepBro