Почему ввод / вывод не перекрывается вычислениями в многопоточном приложении Python? - PullRequest
0 голосов
/ 26 декабря 2018

Я написал простой скрипт на Python для проверки перекрытия между потоками, связанными с вводом-выводом и процессором.Код здесь:

from datetime import datetime
import threading
import shutil
import os


def cpuJob(start,end):
    counter=start
    sum=0
    while counter<=end:
        sum+=counter
        counter+=1
    return sum


def ioJob(from_path, to_path):
    if os.path.exists(to_path):
        shutil.rmtree(to_path)
    shutil.copytree(from_path, to_path)

startTime=datetime.now()

Max=120000000
threadCount=2

if threadCount==1:
    t1 = threading.Thread(target=cpuJob, args=(1,Max))
    # t1 = threading.Thread(target=ioJob, args=(1,Max))
    t1.start()
    t1.join()
else:
    t1 = threading.Thread(target=ioJob, args=("d:\\1","d:\\2"))
    t2 = threading.Thread(target=cpuJob, args=(1,Max))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

endTime=datetime.now()

diffTime = endTime - startTime

print("Execution time for " , threadCount , " threads is: " , diffTime)

Если я запускаю потоки отдельно (threadCount == 1), каждый поток занимает около 12-13 секунд на моем ноутбуке с Windows.Но когда я запускаю их вместе (threadCount == 2), это занимает около 20-22 секунд.Насколько я знаю, Python выпускает GIL перед выполнением каких-либо блокирующих операций ввода-вывода.Если GIL выпускается до работы с вводом / выводом, почему я получаю такую ​​производительность в коде?

Редактировать 1: Как было предложено в комментариях, я проверил код shutils,Похоже, что при реализации этого пакета GIL не выпускается.Почему это так?Код пакета утилит оболочки должен выходить за пределы реализации Python, не так ли?

Ответы [ 2 ]

0 голосов
/ 15 января 2019

Согласно /usr/lib/python3.6/shutil.py на моей машине, эти функции rmtree, copytree и т. Д. Реализованы в виде кода Python, например _rmtree_unsafe.Базовый API, стоящий за rmtree и т. Д., Подобен os.listdir и os.unlink.

Из-за ограничения Python GIL только один поток может одновременно выполнять код Python.Поэтому ваши cpuJob и ioJob не могут работать одновременно (параллельно), потому что оба являются чистым кодом Python, поэтому вы не наблюдаете какого-либо улучшения производительности при попытке запустить их как «потоки».

0 голосов
/ 27 декабря 2018

... почему я получаю такую ​​производительность?

См. https://docs.python.org/3/library/threading.html:

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

Ваш код выполняется в среде без вытеснения, но он никогда не дает управление, покавыходы.Так что другой поток не будет запланирован до тех пор.Вы использовали несколько потоковых машин, но вы также могли написать двухстрочную последовательную функцию, которая вызывает io_job(), за которым следует cpu_job().

. Что вам нужно, так это многопроцессорная обработка .

Кроме того, если вы буквально хотите копировать деревья файлов с помощью таких инструментов, как rsync, рассмотрите возможность использования gmake -jN или GNU параллельно (sudo apt install parallel).Вот пример команды:

$ find . -name '*.txt' -type f | parallel gzip -v9

Как make, так и / usr / bin / parallel позволяют вам указать число одновременных рабочих и будут продолжать рисовать новую задачу из очереди каждый раз, когда рабочий завершает задачу.

...