Что такое многопроцессорность (multiprocessing) в Python?

Ответ

Многопроцессорность (multiprocessing) — это подход к параллельному выполнению задач, при котором операционная система запускает несколько независимых процессов. Каждый процесс имеет собственное адресное пространство памяти, свои ресурсы CPU и не разделяет состояние с другими процессами.

Цель многопроцессорности:

Основная цель — достижение истинного параллелизма, позволяющего эффективно использовать все доступные ядра процессора для выполнения CPU-bound задач (задач, ограниченных вычислительной мощностью процессора, например, сложные расчеты, обработка изображений).

Отличие от многопоточности:

В отличие от потоков (threads), которые выполняются в рамках одного процесса и разделяют его память, процессы полностью изолированы. Это обеспечивает:

  • Изоляцию памяти: Отсутствие общих данных по умолчанию исключает проблемы с состоянием и race conditions, характерные для многопоточности.
  • Устойчивость: Сбой одного процесса не приводит к краху всей программы, так как другие процессы продолжают работать независимо.

Многопроцессорность в Python:

В Python модуль multiprocessing позволяет создавать и управлять процессами. Он является ключевым для обхода ограничения Global Interpreter Lock (GIL), которое препятствует истинному параллелизму в многопоточных приложениях на одном интерпретаторе Python. Каждый процесс запускает свой собственный интерпретатор Python, тем самым обходя GIL.

Пример использования multiprocessing:

from multiprocessing import Process, current_process
import os
import time

def worker(name: str):
    """Функция, выполняемая в отдельном процессе."""
    print(f"Процесс {name} (PID: {os.getpid()}) начал работу.")
    time.sleep(2) # Имитация выполнения CPU-bound задачи
    print(f"Процесс {name} (PID: {os.getpid()}) завершил работу.")

if __name__ == '__main__':
    print(f"Главный процесс (PID: {os.getpid()}) начал работу.")
    processes = []
    for i in range(3):
        # Создаем новый процесс, указывая целевую функцию и аргументы
        p = Process(target=worker, args=(f'Worker-{i}',))
        processes.append(p)
        p.start() # Запускаем процесс

    # Ожидаем завершения всех дочерних процессов
    for p in processes:
        p.join()

    print(f"Главный процесс (PID: {os.getpid()}) завершил работу.")

Преимущества:

  • Истинный параллелизм: Эффективное использование всех доступных ядер процессора для CPU-bound задач.
  • Изоляция: Каждый процесс имеет свою память, что исключает проблемы с состоянием и race conditions.
  • Устойчивость: Сбой одного процесса не влияет на другие, повышая общую надежность системы.

Недостатки:

  • Накладные расходы: Создание и управление процессами требует больше ресурсов (памяти, времени CPU) по сравнению с потоками.
  • Обмен данными: Обмен данными между процессами сложнее и требует специальных механизмов (например, Queue, Pipe, Value, Array из модуля multiprocessing).
  • Не подходит для I/O-bound задач: Для задач, ограниченных вводом/выводом (сеть, диск), асинхронное программирование или многопоточность часто более эффективны из-за меньших накладных расходов.

Ответ 18+ 🔞

А, ну вот, слушай, про многопроцессорность. Это когда ты, блядь, не одного раба заставляешь считать, а целую толпу, и каждый в своей отдельной камере сидит, чтоб не подрались.

Зачем это вообще нужно, спросишь?

А затем, чувак, чтобы задействовать все свои ядра по полной, а не одно ебало, пока остальные в ус не дуют. Для задач, где мозги процессора нужны, а не просто ждать, пока диск скрипнет или сеть ответит. Настоящий параллелизм, ёпта!

А чем это от многопоточности отличается?

Да всем, блядь! Потоки — это как клоуны в одной машине: память одна на всех, дерутся за руль, могут друг другу в тапки нассáть. А процессы — это у каждого своя бронированная тачка, свой бензобак и свой маршрут. Полная изоляция. Один ебнулся — остальные едут дальше, им похуй.

И как это в Питоне работает?

А в Питоне есть модуль multiprocessing. Спасительная штука, чтобы обойти этот ёбаный GIL — такой замок в интерпретаторе, который не даёт потокам работать по-настоящему параллельно. А тут — раз! — каждый процесс свой интерпретатор запускает, и GIL ему не указ. Хуй с горы, а не ограничение.

Смотри, как выглядит эта магия:

from multiprocessing import Process, current_process
import os
import time

def worker(name: str):
    """Функция, выполняемая в отдельном процессе."""
    print(f"Процесс {name} (PID: {os.getpid()}) начал работу.")
    time.sleep(2) # Имитация выполнения CPU-bound задачи
    print(f"Процесс {name} (PID: {os.getpid()}) завершил работу.")

if __name__ == '__main__':
    print(f"Главный процесс (PID: {os.getpid()}) начал работу.")
    processes = []
    for i in range(3):
        # Создаем новый процесс, указывая целевую функцию и аргументы
        p = Process(target=worker, args=(f'Worker-{i}',))
        processes.append(p)
        p.start() # Запускаем процесс

    # Ожидаем завершения всех дочерних процессов
    for p in processes:
        p.join()

    print(f"Главный процесс (PID: {os.getpid()}) завершил работу.")

Что хорошо?

  • Настоящий разгон: Все ядра в деле, CPU-bound задачи летят.
  • Каждый сам за себя: Своя память, нет гонок данных, один не срет в общий суп.
  • Живучесть: Один загнулся — остальные пашут. Не как с потоками — чихнул один, все легли.

Что не очень?

  • Тяжеловесность: Запустить процесс — это не поток создать. Памяти жрёт, времени на старт больше.
  • Общение — геморрой: Чтобы между этими изолированными упырями данные передать, нужны специальные шлюзы (Queue, Pipe). Не просто так переменную ткнуть.
  • Не для всего: Если задача просто ждёт ответа от базы или сети (I/O-bound), то процессы — это как из пушки по воробьям. Тут асинхронность или потоки рулят.