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

В чем связь процессов и потоков в ООП?

2.0 Middle🔥 121 комментариев
#Асинхронность и многопоточность

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

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

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

Процессы и потоки в ООП: связь и различия

Это два фундаментальных концепта параллелизма, которые часто путают. Хотя оба используются для выполнения кода параллельно, они работают совершенно по-разному в ОС и в ООП.

Процесс (Process)

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

Характеристики процесса:

  • Полная изоляция памяти (своя копия всех переменных)
  • Собственное адресное пространство
  • Независимо от других процессов
  • Создание дорого (требует ресурсов)
  • Безопасное (ошибка не влияет на другие)
  • IPC (Inter-Process Communication) для общения
import multiprocessing
import time

def worker(name):
    for i in range(3):
        print(f"{name}: {i}")
        time.sleep(0.1)

# Создание процессов
p1 = multiprocessing.Process(target=worker, args=("Process-1",))
p2 = multiprocessing.Process(target=worker, args=("Process-2",))

p1.start()
p2.start()

p1.join()  # Ждём завершения
p2.join()

print("Готово")

Поток (Thread)

Поток — это лёгкий подпроцесс внутри одного процесса. Все потоки одного процесса разделяют одну и ту же память и ресурсы.

Характеристики потока:

  • Разделяют память (одна копия переменных на всех)
  • Одно адресное пространство (для всех потоков)
  • Зависимы друг от друга
  • Создание дешёвое
  • Рискованно (ошибка влияет на всё)
  • GIL в Python (Global Interpreter Lock) — один поток за раз
import threading
import time

def worker(name):
    for i in range(3):
        print(f"{name}: {i}")
        time.sleep(0.1)

# Создание потоков
t1 = threading.Thread(target=worker, args=("Thread-1",))
t2 = threading.Thread(target=worker, args=("Thread-2",))

t1.start()
t2.start()

t1.join()  # Ждём завершения
t2.join()

print("Готово")

Таблица различий

ПараметрПроцессПоток
НезависимостьПолнаяЗависимы друг от друга
ПамятьСвоя копияРазделённая
Адресное пространствоСвоёОбщее
СозданиеДорогоДешёво
Переключение контекстаДорогоДёшево
БезопасностьБезопасноТребует синхронизации
ОбщениеIPC (сложно)Прямое (небезопасно)
GIL в PythonНетДа (один поток за раз)
СкоростьМедленнееБыстрее
ПримерыChrome вкладкиВеб-сервер обслуживает клиентов

Визуализация памяти

ПРОЦЕССЫ:
Процесс 1        Процесс 2        Процесс 3
[переменные]     [переменные]     [переменные]
[функции]        [функции]        [функции]
[стек]           [стек]           [стек]
ОДНА КОПИЯ       ОДНА КОПИЯ       ОДНА КОПИЯ
(Изолированы друг от друга)

ПОТОКИ (внутри одного процесса):
┌─ Процесс ────────────────────────┐
│ [ОБЩИЕ переменные]                │
│ [ОБЩИЕ функции]                   │
│                                   │
│ Поток 1 ─┐                        │
│ [стек]   │                        │
│          ├─ Используют одни       │
│ Поток 2 ─┤ и те же переменные    │
│ [стек]   │                        │
│          │                        │
│ Поток 3 ─┘                        │
│ [стек]                            │
└───────────────────────────────────┘

ООП контекст: Классы для процессов и потоков

Класс для работы с потоками

from threading import Thread
import time

class Worker(Thread):
    """Рабочий поток (наследуется от Thread)"""
    
    def __init__(self, name):
        super().__init__()  # Инициализация Thread
        self.name = name
        self.daemon = False  # Поток завершится с основной программой
    
    def run(self):  # Переопределяем run() для выполнения в отдельном потоке
        for i in range(5):
            print(f"{self.name}: итерация {i}")
            time.sleep(0.1)

# Использование
worker = Worker("Мой поток")
worker.start()  # Запускает run() в отдельном потоке
worker.join()   # Ждём завершения
print("Готово")

Класс для работы с процессами

from multiprocessing import Process
import time

class Worker(Process):
    """Рабочий процесс (наследуется от Process)"""
    
    def __init__(self, name):
        super().__init__()  # Инициализация Process
        self.name = name
    
    def run(self):  # Переопределяем run() для выполнения в отдельном процессе
        for i in range(5):
            print(f"{self.name}: итерация {i}")
            time.sleep(0.1)

# Использование
if __name__ == "__main__":  # ВАЖНО для Windows!
    worker = Worker("Мой процесс")
    worker.start()  # Запускает run() в отдельном процессе
    worker.join()   # Ждём завершения
    print("Готово")

Проблема разделения памяти в потоках

import threading
import time

class Counter:
    def __init__(self):
        self.value = 0
    
    def increment(self):
        # ПРОБЛЕМА: race condition!
        temp = self.value
        temp += 1
        time.sleep(0.0001)  # Имитация долгой операции
        self.value = temp

counter = Counter()

# Два потока работают с одним объектом
def worker():
    for _ in range(100):
        counter.increment()

t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)

t1.start()
t2.start()
t1.join()
t2.join()

print(f"Значение: {counter.value}")  # Ожидаем 200, получаем <200!

Решение: Lock (Блокировка)

import threading
from threading import Lock
import time

class Counter:
    def __init__(self):
        self.value = 0
        self.lock = Lock()  # Добавляем блокировку
    
    def increment(self):
        with self.lock:  # Критическая секция
            temp = self.value
            temp += 1
            time.sleep(0.0001)
            self.value = temp

counter = Counter()

def worker():
    for _ in range(100):
        counter.increment()

t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)

t1.start()
t2.start()
t1.join()
t2.join()

print(f"Значение: {counter.value}")  # Теперь 200 (правильно!)

Практический пример: Веб-сервер

from threading import Thread
import socket
import time

class WebServer:
    """Простой веб-сервер с потоками"""
    
    def __init__(self, host='localhost', port=8000):
        self.host = host
        self.port = port
        self.socket = None
    
    def handle_client(self, client_socket, address):
        """Обработка одного клиента (в отдельном потоке)"""
        print(f"Подключен: {address}")
        try:
            data = client_socket.recv(1024)
            response = b"HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"
            client_socket.sendall(response)
        finally:
            client_socket.close()
    
    def start(self):
        """Главный сервер цикл"""
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((self.host, self.port))
        self.socket.listen(5)
        print(f"Сервер запущен на {self.host}:{self.port}")
        
        try:
            while True:
                client_socket, address = self.socket.accept()
                # Каждый клиент обрабатывается в отдельном потоке
                client_thread = Thread(target=self.handle_client, args=(client_socket, address))
                client_thread.daemon = True  # Поток завершится с программой
                client_thread.start()
        finally:
            self.socket.close()

# Использование
# server = WebServer()
# server.start()

GIL (Global Interpreter Lock) в Python

GIL — это блокировка в Python, которая позволяет только одному потоку выполнять bytecode одновременно.

import threading
import time

def cpu_bound():
    """CPU-bound работа"""
    total = 0
    for i in range(50000000):
        total += i
    return total

# Однопоточное (без GIL)
start = time.time()
cpu_bound()
cpu_bound()
print(f"Однопоточное: {time.time() - start:.2f}s")  # ~5s

# Двухпоточное (с GIL — медленнее!)
start = time.time()
t1 = threading.Thread(target=cpu_bound)
t2 = threading.Thread(target=cpu_bound)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Двухпоточное: {time.time() - start:.2f}s")  # ~5.5s (медленнее!)

Когда использовать процессы vs потоки

Используйте ПОТОКИ если:

  • I/O-bound задачи (сетевые запросы, файловые операции)
  • Нужно часто переключаться между задачами
  • Нужно разделить данные между задачами
  • Быстро нужно переключение контекста
import threading
import requests

def fetch_url(url):
    response = requests.get(url)
    return len(response.content)

urls = ['http://example.com', 'http://google.com', 'http://github.com']

threads = []
for url in urls:
    t = threading.Thread(target=fetch_url, args=(url,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Используйте ПРОЦЕССЫ если:

  • CPU-bound задачи (сложные вычисления)
  • Нужна полная изоляция
  • Безопасность критична
  • Каждая задача независима
from multiprocessing import Pool

def compute(n):
    return sum(i**2 for i in range(n))

if __name__ == "__main__":
    with Pool(4) as pool:
        results = pool.map(compute, [1000000, 2000000, 3000000, 4000000])
    print(results)

Связь в ООП

В объектно-ориентированном программировании процессы и потоки связаны через наследование:

  • Процесс → класс multiprocessing.Process
  • Поток → класс threading.Thread

Оба наследуются из базовых классов и требуют переопределения метода run() для выполнения пользовательского кода.

Заключение

Процессы: полная независимость, дорого, безопасно. Для CPU-bound работы.

Потоки: разделённая память, дёшево, требует синхронизации. Для I/O-bound работы.

Правило: потоки для веб-сервера и сетевых операций, процессы для вычислений.

В чем связь процессов и потоков в ООП? | PrepBro