#python #multithreading #cpython #gil
#python #многопоточность #cpython #gil
Вопрос:
Обновление: чтобы сэкономить ваше время, я даю ответ прямо здесь. Python не может использовать несколько процессорных ядер одновременно, если вы используете чистый Python для написания своего кода. Но Python может использовать несколько ядер одновременно, когда он вызывает некоторые функции или пакеты, написанные на C, такие как Numpy и т.д.
Я слышал, что «многопоточность в python не является настоящей многопоточностью из-за GIL«. И я также слышал, что «многопоточность python подходит для обработки интенсивной задачи ввода-вывода вместо вычислительно интенсивной задачи, потому что одновременно выполняется только один поток».
Но мой опыт заставил меня переосмыслить этот вопрос. Мой опыт показывает, что даже для вычислительно интенсивной задачи многопоточность python может почти значительно ускорить вычисления. (Для многопоточности мне потребовалось 300 секунд, чтобы запустить следующую программу, после того, как я использую многопоточность, это стоило мне 100 секунд.)
На следующих рисунках показано, что 5 потоков были созданы python
with CPython
в качестве компилятора с пакетом threading
, и все cpu cores
они составляют почти 100% процентов.
Я думаю, что скриншоты могут доказать, что 5 ядер процессора работают одновременно.
Итак, кто-нибудь может дать мне объяснение? Могу ли я применить многопоточность для вычислительно интенсивной задачи в python? Или несколько потоков / ядер могут выполняться одновременно в python?
Мой код:
import threading
import time
import numpy as np
from scipy import interpolate
number_list = list(range(10))
def image_interpolation():
while True:
number = None
with threading.Lock():
if len(number_list):
number = number_list.pop()
if number is not None:
# Make a fake image - you can use yours.
image = np.ones((20000, 20000))
# Make your orig array (skipping the extra dimensions).
orig = np.random.rand(12800, 16000)
# Make its coordinates; x is horizontal.
x = np.linspace(0, image.shape[1], orig.shape[1])
y = np.linspace(0, image.shape[0], orig.shape[0])
# Make the interpolator function.
f = interpolate.interp2d(x, y, orig, kind='linear')
else:
return 1
workers=5
thd_list = []
t1 = time.time()
for i in range(workers):
thd = threading.Thread(target=image_interpolation)
thd.start()
thd_list.append(thd)
for thd in thd_list:
thd.join()
t2 = time.time()
print("total time cost with multithreading: " str(t2-t1))
number_list = list(range(10))
for i in range(10):
image_interpolation()
t3 = time.time()
print("total time cost without multithreading: " str(t3-t2))
вывод:
total time cost with multithreading: 112.71922039985657
total time cost without multithreading: 328.45561170578003
скриншот top
во время многопоточности
скриншот top -H
во время многопоточности
Комментарии:
1. С каким кодом вы его тестировали?
2. Если все они имеют разные PID, то вы, вероятно, случайно используете многопроцессорную обработку.
3. @FiddleStix Да, мне было интересно об этом. Кроме того,
509 total, 508 sleeping
предполагается, что фактическая работа не выполняется.4. @FiddleStix Я использую пакет потоков в python, поэтому я думаю, что я не использовал многопроцессорную обработку.
5. @Carcigenicate Я думаю,
509 total, 508 sleeping
что не дает никакой информации о номере запущенного потока. Это означает номер задачи или инициализацию процесса.
Ответ №1:
Как вы упомянули, Python имеет «глобальную блокировку интерпретатора» (GIL), которая предотвращает одновременное выполнение двух потоков кода Python. Причина, по которой многопоточность может ускорить задачи, связанные с вводом-выводом, заключается в том, что Python освобождает GIL, когда, например, прослушивает сетевой сокет или ожидает чтения с диска. Таким образом, GIL не препятствует одновременному выполнению вашим компьютером двух больших объемов работы, он предотвращает одновременный запуск двух потоков Python в одном и том же процессе Python.
В вашем примере вы используете numpy и scipy. Они в основном написаны на C и используют библиотеки (BLAS, LAPACK и т. Д.), написанные на C / Fortran / Assembly. Когда вы выполняете операции с массивами numpy, это похоже на прослушивание сокета в том смысле, что GIL освобождается. Когда GIL освобождается и вызываются операции с массивом numpy, numpy решает, как выполнить работу. Если он хочет, он может порождать другие потоки или процессы, а вызываемые им подпрограммы BLAS могут порождать другие потоки. Точно, если / как это делается, можно настроить во время сборки, если вы хотите скомпилировать numpy из исходного кода.
Итак, подводя итог, вы нашли исключение из правила. Если бы вы повторили эксперимент, используя только чистые функции Python, вы получили бы совершенно другие результаты (например, см. Раздел «Сравнение» страницы, на которую дана ссылка выше).
Комментарии:
1. Вы имеете в виду, что если я использую функции из пакетов, которые могут быть написаны на C, то несколько потоков могут выполняться одновременно. Но если я использую функции, созданные только на чистом python, то несколько потоков не могут выполняться одновременно. Верно?
2. Да, точно. Я не уверен, освобождает ли интерпретатор Python GIL автоматически или код C (т. Е. numpy в данном случае) должен делать это явно. Другими словами, я не уверен, что это применимо ко всему коду C, вызываемому из Python, вероятно, это зависит от специфики.
Ответ №2:
Потоковая обработка Python — это настоящая потоковая обработка, просто в интерпретаторе не может быть двух потоков одновременно (и это то, о чем GIL). Собственная часть кода может выполняться параллельно без конфликтов в нескольких потоках, только при возврате в интерпретатор им придется сериализовать друг друга.
Тот факт, что у вас все ядра процессора загружены только на 100%, не является доказательством того, что вы используете машину «эффективно». Вам нужно убедиться, что загрузка процессора не связана с переключением контекста.
Если вы переключитесь на многопроцессорную обработку вместо многопоточности (они очень похожи), вам не придется дважды угадывать, но тогда вам придется маршалировать полезную нагрузку при передаче между потоками.
Так что все равно нужно все измерять.