Все потоки Python выполняются на одном ядре

У меня есть программа Python, которая порождает много потоков, работает по 4 за раз, и каждый выполняет дорогостоящую операцию. Псевдокод:

for object in list:
    t = Thread(target=process, args=(object))
    # if fewer than 4 threads are currently running, t.start(). Otherwise, add t to queue

Но когда программа запускается, Activity Monitor в OS X показывает, что 1 из 4 логических ядер на 100%, а остальные - почти на 0. Очевидно, я не могу заставить ОС ничего делать, но я никогда не приходилось обращать внимание на производительность в многопоточном коде, как это раньше, поэтому мне было интересно, если я просто упустил или что-то не понял.

Спасибо.

Ответ 1

Обратите внимание, что во многих случаях (и практически во всех случаях, когда ваша "дорогая операция" является вычислением, реализованным в Python), несколько потоков не будут работать одновременно из-за глобальной блокировки интерпретатора Python (GIL).

GIL - это замок уровня интерпретатора. Эта блокировка предотвращает выполнение нескольких потоков одновременно в интерпретаторе Python. Каждый поток, который хочет работать, должен ожидать освобождения GIL другим потоком, что означает, что ваше многопоточное приложение Python по сути однопоточное, верно? Да. Не совсем. Вроде, как бы, что-то вроде.

CPython использует так называемые потоки "операционной системы", то есть каждый раз, когда делается запрос на создание нового потока, интерпретатор фактически вызывает библиотеки операционных систем и ядро для создания нового потока. Например, это то же самое, что и Java. Таким образом, в памяти у вас действительно есть несколько потоков, и обычно операционная система контролирует, какой поток планируется запустить. На многопроцессорной машине это означает, что вы можете иметь много потоков, распределенных по нескольким процессорам, и все с радостью уйдут с работы.

Однако, хотя CPython действительно использует потоки операционной системы (теоретически, позволяя нескольким потокам выполняться внутри интерпретатора одновременно), интерпретатор также заставляет GIL быть полученным потоком, прежде чем он сможет получить доступ к интерпретатору и стеку и может изменять объекты Python в памяти. все волей-неволей. Последний момент заключается в том, почему существует GIL: GIL предотвращает одновременный доступ к объектам Python несколькими потоками. Но это не спасает вас (как показано в примере с Банком) от существа, чувствительного к блокировке; Вы не получите бесплатную поездку. GIL защищает память переводчиков, а не ваше здравомыслие.

См. Раздел "Глобальная блокировка интерпретатора" публикации Джесси Ноллера для получения более подробной информации.

Чтобы обойти эту проблему, загляните в многопроцессорный модуль Python.

множественные процессы (при разумном использовании IPC) являются [...] гораздо лучшим подходом к написанию приложений для многопроцессорных систем, чем потоки.

- Гвидо ван Россум (создатель Python)

Ответ 2

У Python есть глобальная блокировка переписки, которая может предотвращать одновременную обработку потоков интерпретируемого кода.

http://en.wikipedia.org/wiki/Global_Interpreter_Lock

http://wiki.python.org/moin/GlobalInterpreterLock

Для того, чтобы обойти это, попробуйте модуль многопроцессорности, как описано здесь:

Происходит ли запуск отдельных процессов python в GIL?

Ответ 3

AFAIK, в CPython глобальная блокировка перехватчиков означает, что не может быть более одного блока кода Python, выполняемого в любой момент времени. Хотя это ни на что не влияет ни на один процессор/одноядерный компьютер, на машине с mulitcore это означает, что у вас есть только один поток, запущенный в любой момент времени, в результате чего все остальные ядра будут простаивать.