Python копирование файлов в несколько потоков
3. Знания, связанные с процессами
Анонс — взаимные блокировки в Python
Самое смешное, что по умолчанию GIL защищает только интерпретатор и не предохраняет наш код от взаимных блокировок (deadlock) и других логических ошибок синхронизации. Поэтому разводить потоки по углам, как и в Java, нужно принудительно — с помощью блокирующих механизмов. Об этом и о не упомянутых в статье компонентах модуля threading мы поговорим в следующий раз.
Конфиг
Все параметры для скрипта вынесем в отдельный файл для удобства.
Конфиг сохраняем с расширением .py и импортируем в начале нашего скрипта. Импортировать можно непосредственно в пространство имён скрипта, но я сделал конструкцию слегка напоминающую костыль в основной части моего скрипта:
1. Краткое введение
Python - это кроссплатформенный язык компьютерного программирования. Это объектно-ориентированный язык с динамической типизацией. Первоначально он был разработан для написания автоматизированных сценариев (оболочки). Благодаря постоянному обновлению версии и добавлению новых языковых функций, он все чаще используется для разработки независимых и крупномасштабных проектов. Python - это интерпретируемый язык сценариев, который можно использовать в следующих областях: веб-разработка и Интернет, научные вычисления и статистика, искусственный интеллект, образование, разработка интерфейса рабочего стола, разработка программного обеспечения, внутренняя разработка, веб-сканеры.
Используйте Pycharm для реализации многопроцессорного копировщика файлов папок с использованием пула процессов для копирования всех файлов из одной папки в другую в форме многозадачности.
3. Пул пула процессов
Когда количество дочерних процессов, которые необходимо создать, невелико, вы можете напрямую использовать процесс в многопроцессорной обработке для динамической генерации нескольких процессов, но если есть сотни или даже тысячи целей, рабочая нагрузка по созданию процессов вручную огромна, и вы можете использовать это сейчас. Перейдите к методу Pool, предоставленному модулем многопроцессорности.
При инициализации пула вы можете указать максимальное количество процессов. При отправке нового запроса в пул, если пул не заполнен, будет создан новый процесс для выполнения запроса; но если количество процессов в пуле уже Достигните указанного максимального значения, тогда запрос будет ждать до конца процесса в пуле, будет использовать предыдущий процесс для выполнения новой задачи
Анализ общих функций многопроцессорного пула:
apply_async (func [, args [, kwds]]): используйте неблокирующий режим для вызова func (параллельное выполнение, режим блокировки должен ждать завершения предыдущего процесса перед выполнением следующего процесса), args - это список параметров, переданных в func, а kwds передается Список аргументов ключевого слова для func;
close (): закрыть пул, чтобы он больше не принимал новые задачи;
terminate (): независимо от того, завершена задача или нет, немедленно завершить работу;
join (): основной процесс заблокирован, ожидая выхода дочернего процесса, он должен использоваться после закрытия или завершения;
Как создавать потоки в Python
Метод 2 — «классовый»
Для потока со сложным поведением обычно пишут отдельный класс, который наследуют от Thread из модуля threading. В этом случае программу действий потока прописывают в методе run() созданного класса. Ту же петрушку мы видели и в Java.
PyCUDA и Numba для графики
В графических вычислениях Numba тоже кое-что может. Она умеет работать с программной моделью CUDA, чтобы визуализировать научные данные и работу алгоритмов, выдавать информацию о GPU и др. Подробнее о том, как работают графический процессор и CUDA — здесь. И снова мы встретимся с многопоточностью.
При работе с многомерными массивами в CUDA, чтобы понять, какой поток сейчас работает с элементами массива, нужно отследить, кто и когда вызывает функцию ядра. Например, поток может определять свою позицию в сетке блоков и рассчитать соответствующий элемент массива:
Главный плюс этого кода даже не в скорости исполнения, а в прозрачности и простоте. Снова сошлюсь на Хабр, где есть сравнение скорости GPU-расчетов при использовании Numba, PyCUDA и эталонного С CUDA. Небольшой спойлер: PyCUDA позволяет достичь скорости вычислений, сопоставимой с Cи, а Numba подходит для небольших задач.
Статус-файл
По завершению скачивания скрипт будет записывать в файл уведомление об этом. Это уведомление можно мониторить zabbix, чтобы понимать когда бэкап не отработал или, как сделал я - написать простого бота, чтобы периодически проверять статус.
Класс для работы с этим файлов выглядит так:
4. Очередь в пуле процессов
RuntimeError: Queue objects should only be shared between processes through inheritance.
Ситуация
Нужно было забирать периодически пару сотен файлов с ftp-сервера под Windows. Много мелочи и несколько очень крупных по размеру файлов. Суммарно примерно на 500 Гб. Сервер представляет собой vps, расположенный довольно далеко за рубежом. Днем машина высоко нагружена, рано ночью выполняются регламентные работы, итого на скачивание часов 5 максимум.
Многопоточность
Ну и, наконец, сама основная функция скрипта, которая осуществляет работу с потоками скачивания:
Здесь мы запускаем логирование, получаем список файлов ( он хранится в памяти).
В вечном цикле while мы проверяем количество одновременно запущенных скачиваний и, при необходимости, запускаем дополнительные потоки.
В каких случаях вам нужна многопоточность, как реализовать её на Python и что нужно знать о глобальной блокировке GIL.
Из этой статьи вы узнаете, как с Python выполнять несколько операций одновременно и распределять нагрузку между ядрами процессора, какие особенности языка учитывать. Но главное — поймете, когда многопоточность в Python нужна, а когда только мешает.
Небольшое предупреждение для тех, кто впервые слышит о параллельных вычислениях. Что такое поток и чем он отличается от процесса, мы выяснили в статье «Внутри процесса: многопоточность и пинг-понг mutex'ом». Тогда мы приводили примеры на Java, но теоретические основы многопоточности верны и для Python. Совпадают, в том числе, механизмы синхронизации потоков: семафоры, взаимные исключения (mutex), условия, события. Поэтому сегодня сделаем акцент на особенностях Python, его механизмах и инструментах, связанных с многопоточностью.
Организовать параллельные вычисления в Python без внешних библиотек можно с помощью модулей:
- threading — для управления потоками.
- queue — для организации очередей.
- multiprocessing — для управления процессами.
Пока нас интересует только первый пункт списка.
2. Очередь межпроцессного взаимодействия
Иногда требуется связь между процессами, и операционная система предоставляет множество механизмов для реализации связи между процессами.
Queue.empty (): если очередь пуста, вернуть True, иначе False;
Queue.full (): если очередь заполнена, вернуть True, в противном случае - False;
Queue.get_nowait (): эквивалент Queue.get (False);
В-четвертых, предварительный просмотр эффекта
Numba для математики
Numba — динамически, «на лету» компилирует Python-код, превращая его в машинный код для исполнения на CPU и GPU. Такая технология компиляции называется JIT — “Just in time”. Она помогает оптимизировать производительность программ за счет ускорения работы циклов и компиляции функций при первом запуске.
Суть в том, что вы ставите аннотации (декораторы) в узких местах кода, где вам нужно ускорить работу функций.
Для математических расчётов библиотеку удобно использовать в связке c NumPy. Допустим, нужно сложить одномерные массивы — элемент за элементом.
Метод nupmy.empty_like() принимает массив и возвращает (но не инициализирует!) другой — соответствующий исходному по форме и типу. Чтобы ускорить выполнение кода, импортируем класс jit из модуля numba и добавляем в начало кода аннотацию @jit:
Это скромное дополнение способно ускорить выполнение операции более чем в 100 раз! Если интересно, посмотрите замеры скорости математических расчётов при использовании разных библиотек для Python.
Как использовать многопоточность для ускорения копирования файлов в Windows 7?
Об авторе Эта статья предоставлена MVP Fu Haijun. Спасибо MVP за то, что поделился своей технической информацией и личным опытом.
Основной текст Почему нельзя ускорить функцию копирования / передачи файлов в Windows 7 под Windows 7? Можно использовать robocopy Осуществить реализацию многопоточного копирования файлов.
Друзья, которые часто выполняют операции управления файлами, упоминая операцию копирования / вставки, по-видимому, многие люди вспомнят чрезвычайно медленную скорость копирования Vista в начале и не удовлетворены встроенной функцией копирования системы Windows, потому что это слишком быстрый. Итак, все используют программное обеспечение, такое как FastCopy и TeraCopy, для ускорения копирования. Позднее в Windows 7 эта проблема была исправлена, но эффект оказался неудовлетворительным. Но знаете ли вы, что в Windows 7 есть встроенная функция быстрого копирования?
Если вы хотите скопировать каталог, содержащий несколько файлов большой емкости, вам необходимо использовать команду RoboCopy. Это команда копирования каталога из командной строки. Начиная с Windows NT 4.0, она стала частью набора ресурсов Windows. Затем в В качестве стандартных функций встроены Windows Vista, Windows 7 и Windows Server 2008. Этот инструмент не только имеет очень высокую скорость копирования, но также поддерживает возобновляемую передачу и даже поддерживает копирование в указанное время. Эта функция в Windows 7 также была обновлена - она уже может поддерживать многопоточность, что означает, что скорость репликации может быть значительно увеличена. Кроме того, с помощью этой функции можно создать две полные зеркальные копии файловой структуры без копирования ненужных файлов-дубликатов.Она также позволяет сохранить всю соответствующую информацию о файле, включая дату и время, список управления доступом (ACL) и т. Д. Копирование каталога с более чем N небольшими файлами может увеличить скорость на порядок: несколько тысяч файлов можно скопировать за 3 секунды, в то время как обычный метод копирования и вставки занимает около 45 секунд.
Например, поставить CDF:\Скопируйте все файлы вEDished\CDВ каталоге вы можете выполнить следующую команду:
Robocopy /s F:\ E:\CD
Даже если диск внезапно вынут в это время, это не имеет значения, robocopy будет ждать, пока диск снова не будет вставлен в оптический привод, и копирование начнется автоматически.
Robocopy также поддерживает копирование в локальную сеть и даже может переместить всю структуру каталогов указанного каталога на сервере локальной сети на локальный. Robocopy поддерживает регулярные операции, поэтому операцию копирования можно выполнять ночью, чтобы избежать перегрузки локальной сети и причинения неудобств. другим.
Давайте посмотрим на использование этой команды, как показано на рисунке ниже:
Например: RoboCopy dir_from dir_to / E / MT: 50 /LOG:copy.log
Синтаксис команды: Источник назначения ROBOCOPY [файл [файл] . ] [параметры]
Источник: исходный каталог (диск: \ путь или \\ сервер \ общий ресурс \ путь)
Цель: целевой каталог (диск: \ путь или \\ сервер \ общий ресурс \ путь)
Файл: файл, который нужно скопировать (имя / подстановочный знак: по умолчанию «*. *»)
Чтобы проверить скорость копирования файлов, был проведен следующий тест
Из приведенного выше рисунка видно, что в случае 120 потоков копируется один файл размером 878,16 МБ, начиная с 14:56:31 и заканчивая 14:57:06, что занимает 35 секунд.
Чтобы иметь возможность проверить, сколько потоков можно выбрать для более быстрого копирования файлов, сценарий PowerShell используется для пакетной обработки, чтобы проверить соответствующую взаимосвязь между временем, необходимым для копирования в разных потоках один за другим, и соответствующей взаимосвязью между время и поток отрисовываются по координатам, а тест выполняется. Сценарий PowerShell выглядит следующим образом:
for($i=128;$i –lt 129;$i++)<
Robocopy /s C:\CD C:\test\CD$i /MT:$i /LOG:log/copy$i.log
remove-item C:\\test\CD$i\*.*
"$i ," >>log/answ.txt
(Get-Content C:\log\copy$i.log -TotalCount 6)[-1] >>log/answ.txt
"," >>log/answ.txt
(Get-Content C:\log\copy$i.log -TotalCount 10000)[-1] >>log/answ.txt>
Содержимое вывода скрипта форматируется и импортируется в Excel через формат CSV, а линейная диаграмма строится, как показано на рисунке ниже.Серия 1"линия:
В скопированном целевом каталоге всего 324 файла. Размер одного файла составляет от 1 МБ до 2 МБ. Общий размер файла составляет 549 МБ. По сравнению с одним файлом размером 878,16 МБ, указанным выше, время копирования больше. чем раньше, из-за количества файлов. Их много, копирование одного за другим занимает много времени, и во многих экспериментах будут ошибки. После нормализации, как показано желтой линией "линейная (серия 1)" на на рисунке вы можете видеть то же самое Скорость копирования файловой группы отличается при использовании разных потоков.По мере увеличения количества потоков время копирования уменьшается линейно.
Приложение: Подробные параметры команды RoboCopy
Параметры копирования
/ S: копировать подкаталоги, но не копировать пустые подкаталоги.
/ E: копировать подкаталоги, включая пустые подкаталоги.
/ LEV: n: копировать только первые n уровней исходного дерева каталогов.
/ Z: копировать файлы в перезапускаемом режиме.
/ B: копировать файлы в режиме резервного копирования.
/ ZB: использовать перезапускаемый режим; если доступ запрещен, использовать режим резервного копирования.
/ EFSRAW: копировать все зашифрованные файлы в режиме EFS RAW.
/ COPY: Метка копирования :: Содержимое копируемого файла (по умолчанию / COPY: DAT). (Метка копирования: D = данные, A = атрибут, T = отметка времени). (S = Безопасность = NTFS ACL, O = информация о владельце, U = информация аудита).
/ DCOPY: T: копировать временную метку каталога.
/ SEC: копировать файлы с защитой (эквивалент / COPY: DATS).
/ COPYALL: копировать всю информацию о файле (эквивалент / COPY: DATSOU).
/ NOCOPY: не копировать информацию о файле (используйте с / PURGE, чтобы вступить в силу).
/ SECFIX: восстановить файловую безопасность всех файлов, даже пропущенных файлов.
/ TIMFIX: Восстановить время всех файлов, даже пропущенных файлов.
/ PURGE: удалить целевые файлы / каталоги, которых больше нет в источнике.
/ MIR: зеркало дерева каталогов (эквивалент / E и / PURGE).
/ MOV: переместить файлы (удалить из источника после копирования).
/ MOVE: перемещать файлы и каталоги (удалять из источника после копирования).
/ A +: [RASHCNET]: добавить указанные атрибуты в скопированный файл.
/ A -: [RASHCNET]: удалить данный атрибут из скопированного файла.
/ CREATE: создавать только дерево каталогов и файлы нулевой длины.
/ FAT: использовать для создания целевого файла только имя файла 8.3 FAT.
/ 256: отключить поддержку сверхдлинных путей (> 256 символов).
/ MON: n: отслеживать источник; запускать снова, когда обнаружено более n изменений.
/ MOT: m: контролировать источник; в случае изменения запустить снова в течение m минут.
/ RH: ччмм-ччмм: Часы работы - время, когда может быть запущена новая копия.
/ PF: проверять часы работы для каждого файла (не для каждого шага).
/ IPG: n: интервал между программными пакетами (мс) для освобождения полосы пропускания на низкоскоростных линиях.
/ SL: скопировать символьную ссылку на цель.
/ MT [: n]: использовать n потоков для многопоточной репликации (значение по умолчанию - 8). n должно быть не меньше 1, но не больше 128. Этот параметр несовместим с параметрами / IPG и / EFSRAW. Используйте параметр / LOG для перенаправления вывода для повышения производительности.
Параметры выбора файла
/ A: копировать только файлы с установленным атрибутом архива.
/ M: копировать только файлы с атрибутами архива и сбрасывать атрибуты архива.
/ IA: [RASHCNETO]: включать только файлы с любым заданным набором атрибутов.
/ XA: [RASHCNETO]: исключить файлы с любым заданным набором атрибутов.
/ XF файл [файл] . исключить файлы, соответствующие заданному имени / пути / подстановочному знаку.
/ XD Directory [Directory] . исключить каталоги, соответствующие заданному имени / пути.
/ XC: исключить файлы, которые были изменены.
/ XN: исключить более новые файлы.
/ XO: исключить старые файлы.
/ XX: исключить избыточные файлы и каталоги.
/ XL: исключить потерянные файлы и каталоги.
/ IS: содержать те же файлы.
/ IT: Содержит настроенные файлы.
/ MAX: n: максимальный размер файла без файлов размером более n байтов.
/ MIN: n: минимальный размер файла без файлов размером менее n байт.
/ MAXAGE: n: Самый длинный файл без учета возраста файлов старше n дней / даты.
/ MINAGE: n: самый короткий файл без возрастных ограничений, файлы старше n дней / даты.
/ MAXLAD: n: Максимальное количество файлов, исключая дату последнего доступа, которые не использовались с n.
/ MINLAD: n: Наименьшие файлы с исключением даты последнего доступа, использованные с n. (Если n / XJ: исключить суставы. (Обычно он включен по умолчанию).
/ FFT: Предполагается, что время файла FAT (детализация 2 секунды).
/ DST: компенсировать 1-часовую разницу во времени DST.
/ XJD: Исключить соединения каталогов.
/ XJF: исключить точки соединения файлов.
Параметры повтора
/ R: n: количество попыток для неудачных копий: по умолчанию 1 миллион.
/ W: n: Время ожидания между двумя попытками: по умолчанию 30 секунд.
/ REG: сохранить / R: n и / W: n в реестре как настройки по умолчанию.
/ TBD: Ожидание определения имени общего ресурса (ошибка повтора 67).
Варианты ведения журнала
/ L: только список - не копировать, отмечать время и не удалять файлы.
/ X: сообщать обо всех избыточных файлах, а не только о выбранных файлах.
/ V: генерировать подробный вывод и отображать пропущенные файлы.
/ TS: включить в вывод отметку времени исходного файла.
/ FP: включить в вывод полный путь к файлу.
/ BYTES: размер печати в байтах.
/ NS: Нет размера - не записывать размер файла.
/ NC: Нет категории - Не записывать категорию файла.
/ NFL: Нет списка файлов - не записывать имена файлов.
/ NDL: Нет списка каталогов - не записывать имена каталогов.
/ NP: Нет прогресса - Не отображать процент копирования.
/ ETA: отображать ожидаемое время прибытия скопированных файлов.
/ LOG: File: вывести статус в файл журнала (перезаписать существующий журнал).
/ LOG +: file: вывести статус в файл журнала (добавить в существующий журнал).
/ UNILOG: file: вывести статус в файл журнала в режиме UNICODE (перезаписать существующий журнал).
/ UNILOG +: file: вывести статус в файл журнала в режиме UNICODE (добавить в существующий журнал).
/ TEE: вывод в окно консоли и файл журнала.
/ NJS: Нет описания вакансии.
/ UNICODE: статус вывода в режиме UNICODE.
Варианты работы
/ JOB: Имя задания: извлечь параметры из указанного файла задания.
/ SAVE: Имя задания: сохранить параметры в названном файле задания.
/ QUIT: выйти после обработки командной строки (для просмотра параметров).
/ NOSD: исходный каталог не указан.
/ NODD: целевой каталог не указан.
/ IF: содержит следующие файлы.
Логирование
Для ведения логов скачивания будет использовать стандартную библиотеку logging. Создадим класс, который будет заниматься логированием.
Скрипт будет поддерживать просто логирование в файл и ротацию файловых логов, ибо логи имеют свойство расти непомерно и это надо бы держать под контролем.
Два, можно узнать
1. Использование копии файла Python;
2. Использование многопроцессорности и пула процессов Python;
3. Получить файлы папки Python;
4. Вопросы, требующие внимания: 1) Python имеет строгие требования к коду, Python имеет одну функцию на строку кода;
2) Есть ли у Python строгие отступы кода;
3) Сюда копировать файлы в указанную папку, не включая файлы в папке
4) Чтобы напечатать% в print, введите два% одновременно, например print ("%%")
Приключение начинается. У древнего шлюза
Питон слывёт дружелюбным и простым в общении, но есть у него причуды. Нельзя просто взять и воспользоваться всеми преимуществами многопоточности в Python! Дорогу вам преградит огромный шлюз… Даже так — глобальный шлюз (Global Interpreter Lock, он же GIL), который ограничивает многопоточность на уровне интерпретатора. Технически, это один на всех mutex, созданный по умолчанию. Такого нет ни в C, ни в Java.
Задача шлюза — пропускать потоки строго по одному, чтоб не летали наперегонки, как печально известные стритрейсеры, и не создавали угрозу работе интерпретатора.
Без шлюза потоки подрезали бы друг друга, чтобы первыми добраться до памяти, но это еще не всё. Они имеют обыкновение внезапно засыпать за рулём! Операционная система не спрашивает, вовремя или невовремя — просто усыпляет их в ей одной известный момент. Из-за этого неупорядоченные потоки могут неожиданно перехватывать друг у друга инициативу в работе с общими ресурсами.
Дезориентированный спросонок поток, который видит перед собой совсем не ту ситуацию, при которой засыпал, рискует разбиться и повалить интерпретатор, либо попасть в тупиковую ситуацию (deadlock). Например, перед сном Поток 1 начал работу со списком, а после пробуждения не нашёл в этом списке элементов, т.к. их удалил или перезаписал Поток 2.
Чтобы такого не было, GIL в предсказуемый момент (по умолчанию раз в 5 миллисекунд для Python 3.2+) командует отработавшему потоку: «СПАААТЬ!» — тот отключается и не мешает проезжать следующему желающему. Даже если желающего нет, блокировщик всё равно подождёт, прежде чем вернуться к предыдущему активному потоку.
Благодаря шлюзу однопоточные приложения работают быстро, а потоки не конфликтуют. Но, к сожалению, многопоточные программы при таком подходе выполняются медленнее — слишком много времени уходит на регулировку «дорожного движения». А значит обработка графики, расчет математических моделей и поиск по большим массивам данных c GIL идут неприемлемо долго.
В статье «Understanding Python GIL»технический директор компании Gaglers Inc. и разработчик со стажем Chetan Giridhar приводит такой пример:
Код вычисляет факториал числа 100 000 и показывает, сколько времени ушло у машины на эту задачу. При тестировании на одном ядре и с одним потоком вычисления заняли 3,4 секунды. Тогда Четан создал и запустил второй поток. Расчет факториала на двух ядрах длился 6,2 секунды. А ведь по логике скорость вычислений не должна была существенно измениться! Повторите этот эксперимент на своей машине и посмотрите, насколько медленнее будет решена задача, если вы добавите thread2. Я получила замедление ровно вдвое.
Глобальный шлюз — наследие времён, когда программисты боролись за достойную реализацию многозадачности и у них не очень получалось. Но зачем он сегодня, когда есть много- и очень многоядерные процессоры? Как объяснил Гвидо ван Россум, без GIL не будут нормально работать C-расширения для Python. Ещё упадёт производительность однопоточных приложений: Python 3 станет медленнее, чем Python 2, а это никому не нужно.
Скачивание файла
Каждый файл будет скачиваться в отдельном потоке. Класс, скачивающий один конкретный файл с сервера выглядит следующим образом:
Для подсчета количества одновременно скачиваемых файлов мы будет использовать свойство класса count. В нём у нас будет количество существующих экземпляров класса: в конструкторе счетчик наращивается, в деструкторе, соответственно, уменьшается.
Метод для запуска скачивания должен обязательно называться run - это требование библиотеки threading (не забываем её импортировать!), которую мы будем использовать для параллельного запуска нескольких процессов скачивания.
При сохранении списка файлов скрипт сохраняет также путь до этого файла, этот путь мы воссоздаем при скачивании с помощью os.makedirs.
В начале был список
Для этого нам нужен список этих самых файлов. Ни о каком статичном списке файлов, конечно, речи не идет, значит нам его при каждом выполнении скрипта получать с сервера по-новой.
Удобства ради и чтобы не таскать параметры по всему коду - переопределим параметры стандартного класса ftp:
Параметры берутся из конфига. Конечно же в нужно не забыть импортировать библиотеку ftplib, чтобы этот кусок заработал.
Список файлов с сервера мы получим с помощью следующего класса:
Помимо методов соединения с сервером, получения списка файлов и определения его длины здесь имеет метод, который возвращает нам следующий файл для скачивания, из списка он при этом, конечно, удаляется.
Стандартные методы работы с потоками
Чтобы управлять потоками, нужно следить, как они себя ведут. И для этого в threading есть специальные методы:
current_thread() — смотрим, какой поток вызвал функцию;
active_count() — считаем работающие в данный момент экземпляры класса Thread;
enumerate() — получаем список работающих потоков.
Ещё можно управлять потоком через методы класса:
is_alive() — спрашиваем поток: «Жив ещё, курилка?» — получаем true или false;
getName() — узнаём имя потока;
setName(any_name) — даём потоку имя;
У каждого потока, пока он работает, есть уникальный идентификационный номер, который хранится в переменной ident.
Отсрочить операции в вызываемых потоком функциях можно с помощью таймера. В инициализаторе объектов класса Timer всего два аргумента — время ожидания в секундах и функция, которую нужно в итоге выполнить:
Таймер можно один раз создать, а затем запускать в разных частях кода.
1. Процесс и статус
Программа: например, xxx.py - статическая программа.
Процесс: после запуска программы код + используемые ресурсы называются процессом, который является базовой единицей операционной системы для распределения ресурсов.
Не только многозадачность может выполняться через потоки, но также возможны процессы
2) Статус процесса
В работе количество задач часто превышает количество ядер процессора, то есть должны выполняться некоторые задачи, а другие задачи ожидают выполнения процессора, что приводит к различным состояниям
Состояние готовности: все рабочие условия выполнены и ожидают выполнения на процессоре
Состояние выполнения: процессор выполняет свою функцию
Состояние ожидания: ожидание выполнения определенных условий, например, программа спит, затем она находится в состоянии ожидания
«Нормальные герои всегда идут в обход»
Шлюз можно временно отключить. Для этого интерпретатор Python нужно отвлечь вызовом функции из внешней библиотеки или обращением к операционной системе. Например, шлюз выключится на время сохранения или открытия файла. Помните наш пример с записью строк в файлы? Как только вызванная функция возвратит управление коду Python или интерфейсу Python C API, GIL снова включается.
Как вариант, для параллельных вычислений можно использовать процессы, которые работают изолированно и неподвластны GIL. Но это большая отдельная тема. Сейчас нам важнее найти решение для многопоточности.
Если вы собираетесь использовать Python для сложных научных расчётов, обойти скоростную проблему GIL помогут библиотеки Numba, NumPy, SciPy и др. Опишу некоторые из них в двух словах, чтобы вы поняли, стоит ли разведывать это направление дальше.
Когда многопоточность в Python оправдана
Метод 1 — «функциональный»
Для работы с потоками из модуля threading импортируем класс Thread. В начале кода пишем:
После этого нам будет доступна функция Thread() — с ней легко создавать потоки. Синтаксис такой:
Первый параметр target — это «целевая» функция, которая определяет поведение потока и создаётся заранее. Следом идёт список аргументов. Если судьбу аргументов (например, кто будет делимым, а кто делителем в уравнении) определяет их позиция, их записывают как args=(x,y). Если же вам нужны аргументы в виде пар «ключ-значение», используйте запись вида kwargs=.
Ради удобства отладки можно также дать новому потоку имя. Для этого среди параметров функции прописывают name=«Имя потока». По умолчанию name хранит значение null. А ещё потоки можно группировать с помощью параметра group, который по умолчанию — None.
За дело! Пусть два потока параллельно выводят каждый в свой файл заданное число строк. Для начала нам понадобится функция, которая выполнит задуманный нами сценарий. Аргументами целевой функции будут число строк и имя текстового файла для записи.
Что start() запускает ранее созданный поток, вы уже догадались. Метод join() останавливает поток, когда тот выполнит свои задачи. Ведь нужно закрыть открытые файлы и освободить занятые ресурсы. Это называется «Уходя, гасите свет». Завершать потоки в предсказуемый момент и явно — надёжнее, чем снаружи и неизвестно когда. Меньше риск, что вмешаются случайные факторы. В качестве параметра в скобках можно указать, на сколько секунд блокировать поток перед продолжением его работы.
Стоит ли преодолевать связанные c GIL сложности и тратить время на реализацию многопоточности? Вот примеры ситуаций, когда многопоточность несёт с собой больше плюсов, чем минусов.
- Для длительных и несвязанных друг с другом операций ввода-вывода. Например, нужно обрабатывать ворох разрозненных запросов с большой задержкой на ожидание. В режиме «живой очереди» это долго — лучше распараллелить задачу.
- Вычисления занимают более миллисекунды и вы хотите сэкономить время за счёт их параллельного выполнения. Если операции укладываются в 1 мс, многопоточность не оправдает себя из-за высоких накладных расходов.
- Число потоков не превышает количество ядер. В противном случае параллельной работы всех потоков не получается и мы больше теряем, чем выигрываем.
Когда лучше с одним потоком
- При взаимозависимых вычислениях. Считать что-то в одном потоке и передавать для дальнейшей обработки второму — плохая идея. Возникает лишняя зависимость, которая приводит к снижению производительности, а в случае ошибки — к ступору и краху программы.
- При работе через GIL. Это мы уже выяснили выше.
- Когда важна хорошая переносимость на разных устройствах. Правильно подобрать число потоков для машины пользователя — задача не из легких. Если вы пишете под известное вам «железо», всё можно решить тестированием. Если же нет — понадобится дополнительно создавать гибкую систему подстройки под аппаратную часть, что потребует времени и умения.
Несколько строк основ Python для Python позволяют легко реализовать многопроцессорное копирование файлов папок с использованием пула процессов.
оглавление
Потусторонние потоки
Обычно Python-приложение не завершается, пока работает хоть один его поток. Но есть особые потоки, которые не мешают закрытию программы и останавливается вместе с ней. Их называют демонами (daemons). Проверить, является ли поток демоном, можно методом isDaemon(). Если является, метод вернёт истину.
Назначить поток демоном можно при создании — через параметр “daemon=True” или аргумент в инициализаторе класса.
Не поздно демонизировать и уже существующий поток методом setDaemon(daemonic).
Всё бы ничего, но это даже не верхушка айсберга, потому что прямо сейчас нас ждут великие открытия.
Пять этапов реализации
1) Получите путь к копируемой папке
2) Организуйте целевую папку и создайте целевую папку
3) Получите все общие имена файлов в этой папке
4) Создайте очередь, чтобы подготовиться к достижению прогресса позже
5) Создайте пул процессов и добавьте задачи в пул процессов.
6) Основной процесс показывает прогресс
1. Откройте Pycharm и создайте новый проект, как показано ниже.
2. Создайте в проекте новый скрипт MulitProcessCopyer.py для написания кода копира и настройте его для запуска программы.
3. Импортировать файлы из папки в проекте.
4. Запустите сцену, войдите в Copyer и нажмите Enter, чтобы начать копирование файлов.
Эта статья не для матёрых укротителей Python’а, для которых распутать этот клубок змей — детская забава, а скорее поверхностный обзор многопоточных возможностей для недавно подсевших на питон.
К сожалению по теме многопоточности в Python не так уж много материала на русском языке, а питонеры, которые ничего не слышали, например, про GIL, мне стали попадаться с завидной регулярностью. В этой статье я постараюсь описать самые основные возможности многопоточного питона, расскажу что же такое GIL и как с ним (или без него) жить и многое другое.
Python — очаровательный язык программирования. В нем прекрасно сочетается множество парадигм программирования. Большинство задач, с которыми может встретиться программист, решаются здесь легко, элегантно и лаконично. Но для всех этих задач зачастую достаточно однопоточного решения, а однопоточные программы обычно предсказуемы и легко поддаются отладке. Чего не скажешь о многопоточных и многопроцессных программах.
Многопоточные приложения
В Python есть модуль threading, и в нем есть все, что нужно для многопоточного программирования: тут есть и различного вида локи, и семафор, и механизм событий. Один словом — все, что нужно для подавляющего большинства многопоточных программ. Причем пользоваться всем этим инструментарием достаточно просто. Рассмотрим пример программы, которая запускает 2 потока. Один поток пишет десять “0”, другой — десять “1”, причем строго по-очереди.
Никакой магии и voodoo-кода. Код четкий и последовательный. Причем, как можно заметить, мы создали поток из функции. Для небольших задач это очень удобно. Этот код еще и достаточно гибкий. Допустим у нас появился 3-й процесс, который пишет “2”, тогда код будет выглядеть так:
Мы добавили новое событие, новый поток и слегка изменили параметры, с которыми
стартуют потоки (можно конечно написать и более общее решение с использованием, например, MapReduce, но это уже выходит за рамки этой статьи).
Как видим по-прежнему никакой магии. Все просто и понятно. Поехали дальше.
Global Interpreter Lock
Существуют две самые распространенные причины использовать потоки: во-первых, для увеличения эффективности использования многоядерной архитектуры cоврменных процессоров, а значит, и производительности программы;
во-вторых, если нам нужно разделить логику работы программы на параллельные полностью или частично асинхронные секции (например, иметь возможность пинговать несколько серверов одновременно).
В первом случае мы сталкиваемся с таким ограничением Python (а точнее основной его реализации CPython), как Global Interpreter Lock (или сокращенно GIL). Концепция GIL заключается в том, что в каждый момент времени только один поток может исполняться процессором. Это сделано для того, чтобы между потоками не было борьбы за отдельные переменные. Исполняемый поток получает доступ по всему окружению. Такая особенность реализации потоков в Python значительно упрощает работу с потоками и дает определенную потокобезопасность (thread safety).
Но тут есть тонкий момент: может показаться, что многопоточное приложение будет работать ровно столько же времени, сколько и однопоточное, делающее то же самое, или за сумму времени исполнения каждого потока на CPU. Но тут нас поджидает один неприятный эффект. Рассмотрим программу:
Эта программа просто пишет в файл миллион строк “1” и делает это за ~0.35 секунды на моем компьютере.
Рассмотрим другую программу:
Эта программа создает 2 потока. В каждом потоке она пишет в отдельный файлик по пол миллиона строк “1”. По-сути объем работы такой же, как и у предыдущей программы. А вот со временем работы тут получается интересный эффект. Программа может работать от 0.7 секунды до аж 7 секунд. Почему же так происходит?
Это происходит из-за того, что когда поток не нуждается в ресурсе CPU — он освобождает GIL, а в этот момент его может попытаться получить и он сам, и другой поток, и еще и главный поток. При этом операционная система, зная, что ядер много, может усугубить все попыткой распределить потоки между ядрами.
UPD: на данный момент в Python 3.2 существует улучшенная реализация GIL, в которой эта проблема частично решается, в частности, за счет того, что каждый поток после потери управления ждет небольшой промежуток времени до того, как сможет опять захватить GIL (на эту тему есть хорошая презентация на английском)
«Выходит на Python нельзя писать эффективные многопоточные программы?», — спросите вы. Нет, конечно, выход есть и даже несколько.
Многопроцессные приложения
Для того, чтобы в некотором смысле решить проблему, описанную в предыдущем параграфе, в Python есть модуль subprocess. Мы можем написать программу, которую хотим исполнять в параллельном потоке (на самом деле уже процессе). И запускать ее в одном или нескольких потоках в другой программе. Такой способ действительно ускорил бы работу нашей программы, потому, что потоки, созданные в запускающей программе GIL не забирают, а только ждут завершения запущенного процесса. Однако, в этом способе есть масса проблем. Основная проблема заключается в том, что передавать данные между процессами становится трудно. Пришлось бы как-то сериализовать объекты, налаживать связь через PIPE или друге инструменты, а ведь все это несет неизбежно накладные расходы и код становится сложным для понимания.
Здесь нам может помочь другой подход. В Python есть модуль multiprocessing. По функциональности этот модуль напоминает threading. Например, процессы можно создавать точно так же из обычных функций. Методы работы с процессами почти все те же самые, что и для потоков из модуля threading. А вот для синхронизации процессов и обмена данными принято использовать другие инструменты. Речь идет об очередях (Queue) и каналах (Pipe). Впрочем, аналоги локов, событий и семафоров, которые были в threading, здесь тоже есть.
Кроме того в модуле multiprocessing есть механизм работы с общей памятью. Для этого в модуле есть классы переменной (Value) и массива (Array), которые можно “обобщать” (share) между процессами. Для удобства работы с общими переменными можно использовать классы-менеджеры (Manager). Они более гибкие и удобные в обращении, однако более медленные. Нельзя не отметить приятную возможность делать общими типы из модуля ctypes с помощью модуля multiprocessing.sharedctypes.
Еще в модуле multiprocessing есть механизм создания пулов процессов. Этот механизм очень удобно использовать для реализации шаблона Master-Worker или для реализации параллельного Map (который в некотором смысле является частным случаем Master-Worker).
Из основных проблем работы с модулем multiprocessing стоит отметить относительную платформозависимость этого модуля. Поскольку в разных ОС работа с процессами организована по-разному, то на код накладываются некоторые ограничения. Например, в ОС Windows нет механизма fork, поэтому точку разделения процессов надо оборачивать в:
Впрочем, эта конструкция и так является хорошим тоном.
Что еще .
Для написания параллельных приложений на Python существуют и другие библиотеки и подходы. Например, можно использовать Hadoop+Python или различные реализации MPI на Python (pyMPI, mpi4py). Можно даже использовать обертки существующих библиотек на С++ или Fortran. Здесь можно было упомянуть про такие фреймфорки/библиотеки, как Pyro, Twisted, Tornado и многие другие. Но это все уже выходит за пределы этой статьи.
Если мой стиль вам понравился, то в следующей статье постараюсь рассказать, как писать простые интерпретаторы на PLY и для чего их можно применять.
Однажды передо мной встала задача копирования большого количества файлов с ftp-сервера. Нужно было делать бэкап. Казалось бы, что может быть проще! Но увы, ничего готового работающего так же быстро для моих условий найти не удалось.
Читайте также: