Какой вид многозадачности (потоки или процессы) потребляет больше системных ресурсов?

Ответ

Многопроцессность (multiprocessing) потребляет значительно больше системных ресурсов, чем многопоточность (threading).

Это обусловлено фундаментальными различиями в их архитектуре:

  • Процессы (Multiprocessing):

    • Каждый процесс имеет собственное, полностью независимое адресное пространство памяти и свои ресурсы операционной системы (файловые дескрипторы, таблицы страниц и т.д.).
    • Создание нового процесса требует выделения значительного объема памяти и ресурсов ОС, что является дорогостоящей операцией.
    • Переключение контекста между процессами (context switching) также относительно затратно, так как ОС должна сохранять и восстанавливать состояние всего адресного пространства.
    • Обмен данными между процессами (IPC - Inter-Process Communication) требует специальных механизмов (пайпы, очереди, общая память), что добавляет накладные расходы.
  • Потоки (Threading):

    • Потоки одного процесса разделяют общее адресное пространство памяти и большинство ресурсов ОС.
    • Создание нового потока требует меньше ресурсов, так как не нужно выделять новое адресное пространство.
    • Переключение контекста между потоками внутри одного процесса значительно дешевле, поскольку ОС не нужно менять таблицы страниц памяти.
    • Обмен данными между потоками происходит через общую память, что быстрее, но требует синхронизации (мьютексы, семафоры) для предотвращения состояний гонки.

Особенности Python: В Python, из-за наличия Global Interpreter Lock (GIL), потоки не могут выполнять код Python параллельно на нескольких ядрах CPU. GIL позволяет выполнять только один поток Python за раз. Поэтому:

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

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

# CPU-bound задача: используем multiprocessing для реального параллелизма
from multiprocessing import Pool
import os

def calculate_square(number):
    # Имитация интенсивной CPU-операции
    return number * number

if __name__ == '__main__':
    print(f"Running CPU-bound task with {os.cpu_count()} processes...")
    with Pool(processes=os.cpu_count()) as pool:
        results = pool.map(calculate_square, range(100000))
    # print(results[:10]) # Вывод первых 10 результатов

# I/O-bound задача: используем threading для эффективного ожидания I/O
import threading
import time

def fetch_data(url):
    # Имитация сетевого запроса или чтения файла
    print(f"[Thread {threading.current_thread().name}] Fetching data from {url}...")
    time.sleep(1) # Имитация задержки I/O
    print(f"[Thread {threading.current_thread().name}] Finished fetching from {url}.")
    return f"Data from {url}"

if __name__ == '__main__':
    urls = [f"http://example.com/{i}" for i in range(5)]
    threads = []
    print("nRunning I/O-bound task with threads...")
    for i, url in enumerate(urls):
        thread = threading.Thread(target=fetch_data, args=(url,), name=f"Worker-{i}")
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()
    print("All I/O-bound tasks completed.")