С появлением Python 3 было немало шума по поводу «асинхронности» и «параллелизма», можно предположить, что Python недавно представил эти функции/концепции.
Но это неправда.
Мы использовали эти операции много раз.
Кроме того, новички могут подумать, что asyncio — единственный или лучший способ воссоздать и использовать асинхронные/параллельные операции.
В этой статье мы рассмотрим разные способы достижения параллелизма, их преимущества и недостатки.
Значение терминов:
Прежде чем мы углубимся в технические аспекты, важно иметь некоторое базовое понимание терминов, часто используемых в этом контексте.Синхронный и асинхронный: В синхронный В операциях задачи выполняются одна за другой.
В асинхронный задачи могут запускаться и завершаться независимо друг от друга.
Одна асинхронная задача может запускаться и продолжать выполняться, пока выполнение переходит к новой задаче.
Асинхронные задачи не блокировать (не заставлять вас ждать завершения задачи) операции и обычно работать в фоновом режиме.
Например, вам следует обратиться в туристическое агентство, чтобы спланировать свой следующий отпуск.
Вам необходимо отправить письмо своему менеджеру перед вылетом.
В синхронном режиме вы сначала позвоните в турфирму, и если вас попросят подождать, вы будете ждать, пока вам ответят. Затем вы начнете писать письмо своему руководителю.
Таким образом, вы выполняете задачи последовательно, одно за другим.
[синхронное исполнение, прим.
переводчик] Но, если ты умный, то тебя пока попросили подождать [повесьте трубку, прим.
переводчик] вы начнете писать электронное письмо, и когда с вами снова заговорят, вы приостановите написание, поговорите, а затем закончите письмо.
Вы также можете попросить друга позвонить в агентство и написать письмо самостоятельно.
Это асинхронно, задачи не блокируют друг друга.
Параллелизм и параллелизм: Параллелизм подразумевает совместное выполнение двух задач.
В нашем предыдущем примере, когда мы рассматривали асинхронный пример, мы постепенно переходили от написания электронного письма к разговору с туром.
агентство.
Этот конкурентоспособность .
Когда мы попросили друга позвонить, а сами написали письмо, задания были выполнены.
параллельный .
Параллелизм, по сути, является формой параллелизма.
Но параллелизм зависит от аппаратного обеспечения.
Например, если у ЦП только одно ядро, две задачи не могут выполняться параллельно.
Они просто делят между собой процессорное время.
Тогда это параллелизм, а не параллелизм.
Но когда у нас есть несколько ядер [как и друг в предыдущем примере, который является вторым ядром, прим.
переводчик] мы можем выполнять несколько операций (в зависимости от количества ядер) одновременно.
Подведем итоги:
- Синхронность: блокирует операции (блокировка)
- Асинхронный: не блокирует операции (неблокирующий).
- Конкурентоспособность: совместный прогресс (совместный)
- Параллелизм: параллельный прогресс (параллельный)
Но параллелизм не всегда означает параллелизм.
Потоки и процессы
Python поддерживает потоки уже очень давно.Потоки позволяют выполнять операции одновременно.
Но есть проблема с Глобальная блокировка переводчика (GIL) из-за чего потоки не могли обеспечить настоящий параллелизм.
И все же с появлением многопроцессорность с Python можно использовать несколько ядер.
Потоки Давайте посмотрим на небольшой пример.
В следующем коде функция рабочий будет выполняться в нескольких потоках асинхронно и одновременно.
И вот пример вывода:import threading import time import random def worker(number): sleep = random.randrange(1, 10) time.sleep(sleep) print("I am Worker {}, I slept for {} seconds".
format(number, sleep)) for i in range(5): t = threading.Thread(target=worker, args=(i,)) t.start() print("All Threads are queued, let's see when they finish!")
$ python thread_test.py
All Threads are queued, let's see when they finish!
I am Worker 1, I slept for 1 seconds
I am Worker 3, I slept for 4 seconds
I am Worker 4, I slept for 5 seconds
I am Worker 2, I slept for 7 seconds
I am Worker 0, I slept for 9 seconds
Таким образом мы запустили 5 потоков для совместной работы и после их запуска (то есть после запуска рабочей функции) операция не ждет Потоки завершаются перед переходом к следующему оператору печати.
Это асинхронная операция.
В нашем примере мы передали функцию конструктору Thread. Если бы мы захотели, мы могли бы реализовать подкласс с методом (стиль ООП).
Дальнейшее чтение: Чтобы узнать больше о потоках, воспользуйтесь ссылкой ниже:
Глобальная блокировка переводчика (GIL) GIL был введен, чтобы упростить обработку памяти CPython и обеспечить лучшую интеграцию с C (например, расширения).GIL — это механизм блокировки, при котором интерпретатор Python одновременно запускает только один поток.
Те.
только один поток может одновременно выполнять байт-код Python. GIL гарантирует, что несколько потоков не выполняются параллельный .
Краткая информация о ГИЛ:
- Одновременно может выполняться один поток.
- Интерпретатор Python переключается между потоками для достижения параллелизма.
- GIL применим к CPython (стандартная реализация).
Но такие как, например, Jython и IronPython не имеют GIL.
- GIL делает однопоточные программы быстрыми.
- GIL обычно не мешает операциям ввода-вывода.
- GIL упрощает интеграцию непотокобезопасных библиотек C, благодаря GIL у нас есть множество высокопроизводительных расширений/модулей, написанных на C.
- Для задач, зависящих от процессора, интерпретатор проверяет каждые N тактов и переключает потоки.
Таким образом, один поток не блокирует другие.
Я рассматриваю это как благо, ведь были созданы такие библиотеки, как NumPy и SciPy, занимающие особое, уникальное положение в научном сообществе.
Дальнейшее чтение: Эти ресурсы позволят вам глубже изучить GIL:
- www.dabeaz.com/python/UnderstandingGIL.pdf
- Статья на русском языке.
[ок.
переводчик]
- Еще немного о ГИЛ.
[ок.
переводчик]
Давайте просто изменим предыдущий пример.
Теперь модифицированная версия использует Процесс вместо Поток .
import multiprocessing
import time
import random
def worker(number):
sleep = random.randrange(1, 10)
time.sleep(sleep)
print("I am Worker {}, I slept for {} seconds".
format(number, sleep))
for i in range(5):
t = multiprocessing.Process(target=worker, args=(i,))
t.start()
print("All Processes are queued, let's see when they finish!")
Что изменилось? Я только что импортировал модуль многопроцессорность вместо резьба .
И тогда вместо потока я использовал процесс.
Вот и все! Теперь вместо нескольких потоков мы используем процессы, работающие на разных ядрах ЦП (если, конечно, ваш процессор многоядерный).
С помощью класса Pool мы также можем распределить выполнение одной функции между несколькими процессами для разных входных значений.
Пример из официальных документов: from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
p = Pool(5)
print(p.map(f, [1, 2, 3]))
Здесь вместо того, чтобы перебирать список значений и вызывать функцию f по одному, мы фактически запускаем функцию в разных процессах.
Один процесс выполняет f(1), другой выполняет f(2), а третий выполняет f(3).
Наконец, результаты снова объединяются в список.
Это позволяет нам разбивать тяжелые вычисления на более мелкие части и запускать их параллельно для более быстрых вычислений.
Дальнейшее чтение:
модуль concurrent.futures Модуль concurrent.futures большой и упрощает написание асинхронного кода.Мои любимые ThreadPoolExecutor И ProcessPoolExecutor .
Эти исполнители поддерживают пул потоков или процессов.
Мы отправляем наши задачи в пул, и он запускает их в доступном потоке/процессе.
Объект возвращается Будущее , который можно использовать для запроса и получения результата после завершения задачи.
А вот пример ThreadPoolExecutor: from concurrent.futures import ThreadPoolExecutor
from time import sleep
def return_after_5_secs(message):
sleep(5)
return message
pool = ThreadPoolExecutor(3)
future = pool.submit(return_after_5_secs, ("hello"))
print(future.done())
sleep(5)
print(future.done())
print(future.result())
У меня есть статья о concurrent.futures masnun.com/2016/03/29/python-a-quick-introduction-to-the-concurrent-futures-module.html .
Это может быть полезно при более глубоком изучении этого модуля.
Дальнейшее чтение:
Асинсио — что, как и почему?
У вас, вероятно, возник вопрос, который возникает у многих людей в сообществе Python: что нового дает asyncio? Зачем нужен был другой метод асинхронного ввода-вывода? Разве у нас уже не было потоков и процессов? Давай посмотрим! Зачем нам нужен asyncio? Процессы очень дорогие [с точки зрения потребления ресурсов, прим.переводчик] для создания.
Поэтому потоки в основном выбираются для операций ввода-вывода.
Мы знаем, что ввод-вывод зависит от внешних факторов: медленные диски или неприятные задержки в сети делают ввод-вывод часто непредсказуемым.
Теперь предположим, что мы используем потоки для операций ввода-вывода.
3 потока выполняют различные задачи ввода-вывода.
Интерпретатору придется переключаться между конкурирующими потоками и по очереди уделять каждому из них некоторое время.
Назовем потоки Т1, Т2 и Т3. Три потока начали операцию ввода-вывода.
Т3 завершает его первым.
T2 и T1 все еще ждут ввода-вывода.
Интерпретатор Python переключается на T1, но все еще ждет. Хорошо, интерпретатор переходит к T2, который все еще ожидает, а затем переходит к T3, который готов и выполняет код. Вы видите в этом проблему? Т3 был готов, но переводчик сначала переключился между Т2 и Т1 — это влечет за собой затраты на переключение, которых мы могли бы избежать, если бы переводчик сначала переключился на Т3, верно? Что такое асинио? Asyncio предоставляет нам цикл событий и другие интересные вещи.
Цикл событий отслеживает события ввода-вывода и переключает задачи, которые готовы и ожидают операций ввода-вывода.
[цикл событий — программная конструкция, ожидающая прихода и отправляющая события или сообщения в программу, прим.
переводчик] .
Идея очень проста.
Есть цикл событий.
И у нас есть функции, выполняющие асинхронные операции ввода-вывода.
Мы передаем наши функции в цикл обработки событий и просим его выполнить их за нас.
Цикл событий возвращает объект Future как обещание, что мы получим что-то в будущем.
Мы держим обещание, время от времени проверяем, имеет ли оно значение (мы очень нетерпеливы), и наконец, когда значение получено, используем его в каких-то других операциях [те.
Мы отправили запрос, нам сразу выдали тикет и сказали ждать, пока придет результат. Мы периодически проверяем результат и как только он получен берем билет и получаем из него стоимость, ок.
переводчик] .
Asyncio использует генераторы и сопрограммы для остановки и возобновления задач.
Подробности можно прочитать здесь:
- masnun.com/2015/11/20/python-asyncio-future-task-and-the-event-loop.html
- masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html
import asyncio
import datetime
import random
async def my_sleep_func():
await asyncio.sleep(random.randint(0, 5))
async def display_date(num, loop):
end_time = loop.time() + 50.0
while True:
print("Loop: {} Time: {}".
format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
await my_sleep_func()
loop = asyncio.get_event_loop()
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
loop.run_forever()
Обратите внимание, что синтаксис async/await предназначен только для Python 3.5 и выше.
Давайте пройдемся по коду:
- У нас есть асинхронная функция display_date, которая принимает в качестве параметров число (в качестве идентификатора) и цикл событий.
- Функция имеет бесконечный цикл, который прерывается через 50 секунд. Но в этот период она неоднократно набирает время и делает паузу.
Функция await может ожидать завершения выполнения других асинхронных функций (сопрограмм).
- Мы передаем функцию в цикл обработки событий (используя метод обеспечения_будущего).
- Запускаем цикл событий.
Поэтому он приостанавливает выполнение, начинает отслеживать любые связанные с ним события ввода-вывода и разрешает выполнение задач.
Когда asyncio замечает, что приостановленный ввод-вывод функции готов, он возобновляет выполнение функции.
Делаем правильный выбор
Мы только что прошли через самые популярные формы конкуренции.Но остается вопрос – что выбрать? Это зависит от вариантов использования.
По своему опыту я склонен следовать этому псевдокоду: if io_bound:
if io_very_slow:
print("Use Asyncio")
else:
print("Use Threads")
else:
print("Multi Processing")
- Ограничение ЦП => Многопроцессорная обработка
- Ограничение ввода-вывода, быстрый ввод-вывод, ограниченное количество соединений => многопоточность
- Ограничение ввода-вывода, медленный ввод-вывод, множество подключений => Asyncio
-
Оптимизация Gamethread В Unreal Engine 4 Ч.1
19 Oct, 24 -
Seo-Инструментарий
19 Oct, 24 -
Как Добавить Сборку Silverlight В Gac
19 Oct, 24