Поддерживает ли Python многопоточность? Может ли это ускорить время выполнения?

98

Я немного не понимаю, работает ли многопоточность в Python или нет.

Я знаю, что по этому поводу было много вопросов, и я читал многие из них, но все еще не понимаю. Я знаю по собственному опыту и видел, как другие публикуют свои собственные ответы и примеры здесь, в StackOverflow, что многопоточность действительно возможна в Python. Так почему же все продолжают говорить, что Python заблокирован GIL и что одновременно может работать только один поток? Это явно работает. Или есть какое-то различие, которого я здесь не понимаю?

Многие авторы / респонденты также продолжают упоминать, что многопоточность ограничена, потому что не использует несколько ядер. Но я бы сказал, что они по-прежнему полезны, потому что они работают одновременно и, таким образом, быстрее справляются с комбинированной рабочей нагрузкой. Я имею в виду, почему в противном случае мог бы существовать модуль потока Python?

Обновить:

Спасибо за все ответы. Насколько я понимаю, многопоточность будет выполняться только параллельно для некоторых задач ввода-вывода, но может выполняться только по одной для нескольких ядерных задач, связанных с процессором.

Я не совсем уверен, что это означает для меня с практической точки зрения, поэтому я просто приведу пример задачи, которую я хотел бы выполнить в многопоточном режиме. Например, предположим, что я хочу просмотреть очень длинный список строк и выполнить некоторые базовые операции со строками для каждого элемента списка. Если я разделю список, отправлю каждый подсписок для обработки моим кодом цикла / строки в новом потоке и отправлю результаты обратно в очередь, будут ли эти рабочие нагрузки выполняться примерно одновременно? Самое главное, теоретически это ускорит время, необходимое для запуска скрипта?

Другой пример: я могу визуализировать и сохранить четыре разных изображения с помощью PIL в четырех разных потоках, и будет ли это быстрее, чем обработка изображений одно за другим? Думаю, именно этот компонент скорости меня действительно интересует, а не правильная терминология.

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

Карим Бахгат
источник
4
Это довольно сложный вопрос. Я думаю, что ответ заключается в том, что вы хотите, чтобы потоки делали. В большинстве случаев GIL предотвращает одновременное выполнение более 1 потока. Однако есть несколько случаев, когда GIL освобождается (например, чтение из файла), так что это можно делать параллельно. Также обратите внимание, что GIL - это деталь реализации Cpython (наиболее распространенная реализация). Никакая другая реализация python (Jython, PyPy и т. Д.) Не имеет GIL (AFAIK)
mgilson
2
@mgilson PyPy имеет GIL.
2
@delnan - Вы, кажется, правы. Спасибо.
mgilson 05
1
«подпроцессы могут запускаться медленно» - вы можете создать пул задач, готовых к выполнению. Накладные расходы могут быть ограничены примерно временем, которое требуется для сериализации / десериализации данных, необходимых для начала работы задачи.
Брайан Кейн
1
@KarimBahgat, это именно то, что я имел в виду.
Брайан Кейн

Ответы:

136

GIL не препятствует нарезанию потоков. Все, что делает GIL, - это проверяет, что только один поток выполняет код Python за раз; управление по-прежнему переключается между потоками.

В этом случае GIL предотвращает использование более одного ядра ЦП или отдельных ЦП для параллельного выполнения потоков.

Это относится только к коду Python. Расширения C могут выпускать и выпускают GIL, позволяя нескольким потокам кода C и одному потоку Python работать на нескольких ядрах. Это распространяется на операции ввода-вывода, контролируемые ядром, такие как select()вызовы чтения и записи сокетов, благодаря чему Python обрабатывает сетевые события достаточно эффективно в многопоточной многоядерной настройке.

То, что затем делают многие серверные развертывания, - это запуск нескольких процессов Python, чтобы операционная система могла выполнять планирование между процессами для максимального использования ядер вашего процессора. Вы также можете использовать multiprocessingбиблиотеку для обработки параллельной обработки нескольких процессов из одной кодовой базы и родительского процесса, если это соответствует вашим вариантам использования.

Обратите внимание, что GIL применим только к реализации CPython; Jython и IronPython используют разные реализации потоков (собственные потоки времени выполнения Java VM и .NET соответственно).

Чтобы напрямую обратиться к вашему обновлению: любая задача, которая пытается повысить скорость за счет параллельного выполнения с использованием чистого кода Python, не увидит ускорения, поскольку многопоточный код Python привязан к одному потоку, выполняющемуся за раз. Однако, если вы смешиваете расширения C и операции ввода-вывода (например, операции PIL или numpy), и любой код C может работать параллельно с одним активным потоком Python.

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

Мартейн Питерс
источник
Спасибо @MartijnPieters, тогда у меня есть более четкий ответ на мой вопрос о том, можно ли использовать многопоточность для ускорения кода, такого как цикл for, который является «нет». Возможно, вы или кто-то могли бы написать новый ответ, который я могу принять, в котором приведены некоторые конкретные примеры общих модулей / кодов / операций, где потоки будут разрешены GIL для параллельного выполнения и, следовательно, быстрее (например, примеры этих операций ввода-вывода и сети / упомянутые операции чтения сокета и любые другие случаи, когда многопоточность в Python полезна). Может быть, хороший список распространенных применений многопоточности и несколько примеров программирования, если возможно?
Карим Бахгат 06
4
Нет, я не думаю, что такой ответ был бы очень полезным; Если честно. Вы никогда не сможете создать исчерпывающий список, но практическое правило состоит в том, что любой ввод-вывод (чтение и запись файлов, сетевые сокеты, каналы) обрабатывается на C, и многие библиотеки C также выпускают GIL для своих операций, но библиотеки должны задокументировать это за вас.
Мартейн Питерс
1
Плохо, я до сих пор не видел вашего обновленного ответа, в котором вы привели несколько хороших примеров использования потоков. Они включали (поправьте меня, если я ошибаюсь) сетевое программирование (например urllib.urlopen()?), Вызов одного сценария Python из графического интерфейса Python и вызов нескольких операций PIL (например Image.transform()) и numpy (например numpy.array()) с потоками. И вы привели еще несколько примеров в своем комментарии, таких как использование нескольких потоков для чтения файлов (например f.read()?). Я знаю, что исчерпывающий список невозможен, просто хотел типы примеров, которые вы привели в своем обновлении. В любом случае, принял ваш ответ :)
Карим Бахгат
2
@KarimBahgat: Да, urllib.urlopen()вызовет сетевые сокеты, ожидание ввода-вывода сокета - отличная возможность переключать потоки и делать что-то еще.
Мартейн Питерс
4
Хотя это не имеет прямого отношения к этой проблеме, стоит отметить, что иногда многопоточность вообще не связана с производительностью; может быть проще написать код как несколько независимых потоков выполнения. Например, у вас может быть один поток, воспроизводящий фоновую музыку, один обслуживающий пользовательский интерфейс, а другой, занятый вычислениями, которые в конечном итоге должны быть выполнены, но не в спешке. Попытка упорядочить воспроизведение следующего аудиобуфера с помощью цикла выполнения пользовательского интерфейса или разбить вычисления на достаточно мелкие части, чтобы не мешать интерактивности, может быть намного сложнее, чем использование потоков.
abarnert
4

Да. :)

У вас есть низкий уровень резьбы модуля и более высокий уровень многопоточности модуль. Но если вы просто хотите использовать многоядерные машины, многопроцессорность модуль - .

Цитата из документов :

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

зорд
источник
3

В Python разрешена многопоточность, единственная проблема заключается в том, что GIL будет следить за тем, чтобы одновременно выполнялся только один поток (без параллелизма).

Так что в основном, если вы хотите использовать многопоточность кода для ускорения вычислений, это не ускорит его, поскольку за раз выполняется только один поток, но если вы используете его, например, для взаимодействия с базой данных, он будет.

r.guerbab
источник