Python 2.7 concurrent.futures.ThreadPoolExecutor не распараллеливает

Я запускаю следующий код на компьютере с процессором Intel i3 с 4 виртуальными ядрами (2 гиперпотока/физическое ядро, 64 бит) и Ubuntu 14.04:

n = multiprocessing.cpu_count()
executor = ThreadPoolExecutor(n)
tuple_mapper = lambda i: (i, func(i))
results = dict(executor.map(tuple_mapper, range(10)))

Код, похоже, не выполняется параллельно, так как CPU используется только 25% постоянно. На графике использования только один из 4 виртуальных ядер используется на 100% за раз. Используемые ядра чередуются каждые 10 секунд или около того.

Но распараллеливание хорошо работает на серверной машине с одинаковыми настройками программного обеспечения. Я не знаю точного количества ядер или точного типа процессора, но я точно знаю, что он имеет несколько ядер, а его использование составляет 100%, а вычисления имеют быстрое ускорение (в 10 раз быстрее после использования распараллеливания некоторые эксперименты с ним).

Я бы ожидал, что распараллеливание будет работать и на моей машине не только на сервере.

Почему это не работает? Имеет ли это какое-то отношение к моим настройкам операционной системы? Должен ли я их изменить?

Спасибо заранее!

Update: Для получения дополнительной информации см. Правильный ответ ниже. Для полноты я хочу привести пример кода, который решил проблему:

tuple_mapper = lambda i: (i, func(i))
n = multiprocessing.cpu_count()
with concurrent.futures.ProcessPoolExecutor(n) as executor:
    results = dict(executor.map(tuple_mapper, range(10)))

Прежде чем повторно использовать это, убедитесь, что все функции, которые вы используете, определены на верхнем уровне модуля, как описано здесь: Ошибка трассировки многопроцессорности Python

Ответ 1

Похоже, вы видите результаты Python Global Interpreter Lock (a.k.a GIL).

В CPython блокировка глобального интерпретатора или GIL - это мьютекс, который предотвращает выполнение нескольких собственных потоков от выполнения байт-кодов Python в один раз.

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

Вы можете обойти его, используя несколько процессов с ProcessPoolExecutor из того же модуля. Другие решения включают переход на Jython или IronPython, у которых нет GIL.

Класс ProcessPoolExecutor - это подкласс исполнителя, который использует пул процессов для выполнения асинхронных вызовов. ProcessPoolExecutor использует многопроцессорный модуль, , который позволяет ему Interpreter Lock, но также означает, что могут быть только выделяемые объекты выполнен и возвращен.