Для чего используется многопоточность (multithreading) в Python?

Ответ

Многопоточность в Python применяется для совместного выполнения I/O-bound операций (работа с сетью, файлами, базами данных), где потоки могут работать, пока один ожидает ответа. Однако из-за GIL (Global Interpreter Lock) потоки не выполняют CPU-bound задачи параллельно — для этого используется модуль multiprocessing.

Пример для I/O-bound задач (потоки):

import threading
import time

def io_task(task_id):
    print(f"Задача {task_id}: начало ожидания I/O")
    time.sleep(2)  # Имитация I/O-операции
    print(f"Задача {task_id}: завершена")

threads = []
for i in range(3):
    t = threading.Thread(target=io_task, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()
print("Все I/O-задачи завершены.")

Пример для CPU-bound задач (процессы):

from multiprocessing import Process
import math

def cpu_task(n):
    result = sum(math.sqrt(i) for i in range(n))
    print(f"Вычисление завершено, результат: {result:.2f}")

if __name__ == '__main__':
    p = Process(target=cpu_task, args=(1_000_000,))
    p.start()
    p.join()

Ключевые выводы:

  • Потоки (threading) — для операций, связанных с ожиданием (I/O).
  • Процессы (multiprocessing) — для параллельных вычислений (CPU).
  • GIL ограничивает выполнение только одного потока Python одновременно, поэтому потоки не ускоряют CPU-bound код.

Ответ 18+ 🔞

Да блядь, слушай, вот тебе про многопоточность в Питоне, чтоб ты понимал, в чём тут собака зарыта. Это ж не просто так, сука, взял и накрутил потоков — нихуя не выйдет, если не знать, где их применять.

Вот смотри, если у тебя задачи I/O-bound — это когда твоя программа тупо ждёт, как лох: ответ от сервера, чтение с диска, запрос в базу данных. Вот тут потоки — самое то, блядь! Пока один поток спит, ожидая ответа, другие могут работать. Это как в очереди в поликлинике: пока один идёт на приём, остальные сидят и хуйней страдают.

Вот, держи пример, чтоб глаза на лоб не полезли:

import threading
import time

def io_task(task_id):
    print(f"Задача {task_id}: начало ожидания I/O")
    time.sleep(2)  # Имитация I/O-операции
    print(f"Задача {task_id}: завершена")

threads = []
for i in range(3):
    t = threading.Thread(target=io_task, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()
print("Все I/O-задачи завершены.")

Видишь? Три задачи стартанули почти одновременно, потому что они ждут, блядь. А не считают.

А теперь, внимание, ёпта! Если у тебя задачи CPU-bound — это когда процессор пашет, как папа Карло, вычисляя что-то тяжёлое (типа перемножения матриц или там поиска простых чисел) — тут потоки нихуя не помогут! Вообще! Потому что в Питоне есть такая хуйня, называется GIL (Global Interpreter Lock). Это такая сраная заглушка, которая позволяет в один момент времени работать только одному потоку с байт-кодом Питона. Представь, у тебя восемь ядер, а работает только одно, остальные семь — просто болтаются, как сиськи на бабушке. Пиздец, да?

Поэтому для CPU-задач надо использовать процессы, блядь! У каждого процесса свой интерпретатор, свой GIL, и они могут на разных ядрах реально параллельно работать. Вот, смотри, как это выглядит:

from multiprocessing import Process
import math

def cpu_task(n):
    result = sum(math.sqrt(i) for i in range(n))
    print(f"Вычисление завершено, результат: {result:.2f}")

if __name__ == '__main__':
    p = Process(target=cpu_task, args=(1_000_000,))
    p.start()
    p.join()

Видишь разницу? Это уже не потоки, а отдельный процесс. Он запустится и будет пахать сам по себе, не мешая основному.

Итог, чтоб ты запомнил, как Отче наш:

  • Потоки (threading) — для операций, где много ожидания (I/O). Сеть, файлы, базы. Пока один ждёт, другие работают.
  • Процессы (multiprocessing) — для тяжёлых вычислений (CPU). Каждый процесс на своём ядре, GIL не мешает.
  • GIL — это такая питоновская придурь, из-за которой потоки нихуя не ускоряют вычисления. Просто запомни: для счёта — процессы, для ожидания — потоки. Всё, вопрос закрыт, иди работай.