Этот вопрос проверяет, понимаете ли вы реальные ограничения многопоточности в Python и отличаете ли логический параллелизм от физического.
В CPython два потока не могут одновременно выполнять байткод Python из-за GIL. Однако потоки могут выполняться “параллельно”, если один из них ждет I/O или если код работает вне GIL. Также реальное одновременное выполнение возможно при использовании нескольких процессов. Поэтому ответ зависит от типа задачи и используемого подхода.
Чтобы ответить корректно, нужно разделять видимость параллельности и реальное одновременное выполнение.
Определение: Одновременное выполнение потоков — это ситуация, когда инструкции выполняются физически параллельно на разных ядрах процессора, а не просто быстро чередуются.
Важно сначала понять базовое ограничение.
Байткод Python
В каждый момент времени его выполняет только один поток.
Это жесткое ограничение GIL.
Переключение ≠ параллельность
Потоки быстро сменяют друг друга, создавая ощущение одновременности.
На самом деле выполнение идет по очереди.
Несмотря на GIL, есть несколько условий, при которых потоки могут выполняться одновременно.
Перед перечислением важно отметить: в этих случаях параллельность достигается не за счет CPU.
Поток, ожидающий сеть или диск, освобождает GIL.
В это время другой поток выполняет Python-код.
Это не параллельное вычисление, но эффективное использование времени ожидания.
Некоторые библиотеки (например, numpy) освобождают GIL во время вычислений.
Пока один поток считает в C, другой может выполнять Python-код.
Здесь возможно реальное использование нескольких ядер.
Каждый процесс имеет свой интерпретатор и свой GIL.
Процессы реально выполняются параллельно на разных ядрах.
Это основной способ масштабировать CPU-bound задачи в Python.
Пример с процессами:
from multiprocessing import Process
def work():
x = 0
for _ in range(10_000_00):
x += 1
p1 = Process(target=work)
p2 = Process(target=work)
p1.start(); p2.start()
p1.join(); p2.join()
В CPython потоки не выполняют байткод параллельно, но могут эффективно перекрывать ожидание I/O; для настоящего параллелизма CPU-задач стоит использовать процессы или C-расширения.