Asyncio, threading, multiprocessing — как пользоваться

В этой статье мы коротко разберем такие библиотеки, как asyncio, threading, multiprocessing, коротко о них и как работают.

прОцесс пошел!

1.Asyncio

asyncio - библиотека, которая работает в режиме цикла событий с незаблокированным API,все события работают в 1 потоке в асинхронном режиме.

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

Приведу визуальный пример, а потом мы его рассмотрим в коде

В данном примере Иван это функция ivan, а машина это функция car, которые выполняют свои задачи
В данном примере Иван это функция ivan, а машина это функция car, которые выполняют свои задачи
import asyncio async def ivan(): print(" (1) // Иван идет по дороге и тут светофор! надо подождать...") await asyncio.sleep(2) print(" (2) // Загорается зеленый свет для пешеходов и мы идем дальше") async def car(): print(" (3) // Машина начинает ехать") await asyncio.sleep(2) print(" (4) // Загорается красный для пешеходов и снова начинают ехать машины") if __name__ == "__main__": event_l = asyncio.get_event_loop() tasks = [event_l.create_task(ivan()), event_l.create_task(car())] wait_tasks = asyncio.wait(tasks) event_l.run_until_complete(wait_tasks) event_l.close()

1) Выполняем часть задачи в функции ivan и начинаем ждать «что-то»

2) Пока ожидается «что-то» в функции ivan, происходит переход к выполнению задачи в функции car, тут выполняется часть задачи и мы начинаем ждать там «что-то», в этот момент происходит переход обратно к функции ivan

3)Так как мы перестали ждать «что-то" в функции ivan, завершаем задачу, пока функция car ждала "что-то», задача в функции ivan завершилась.

4) Функция car перестает ждать «что-то», и до конца выполняется задача

2.Threading

threading — библиотека, которая позволяет работать с несколькими потоками(количество ограничено только возможностями вашей машины).

Здесь нет асинхронной работы, просто каждая задача запускается в отдельном потоке, где все потоки работают параллельно(почти)

Упс… упираемся в GIL при многопоточной работе… как же так ?!

Сколько бы вы не создавали потоков в Python, все это будет скомкано в GIL, где в реальном времени может работать только 1 поток, просто во время работы программы будет происходить переключение между этими потоками.

Приведу визуальный пример

Как вы видите, в реальном времени может работать только 1 поток, просто GIL переключается между ними в зависимости от того какому потоку сейчас нужен GIL, но вопрос приоритетности оставим на потом.
Как вы видите, в реальном времени может работать только 1 поток, просто GIL переключается между ними в зависимости от того какому потоку сейчас нужен GIL, но вопрос приоритетности оставим на потом.

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

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

Запускаем задачу в разных потоках вот так:

from threading import Thread def task_1(a: int): b = a * a c = b + 400 d = 0 for i in range(600): d += i d += c print(f"Результат вычислений: {d}") def task_2(a: int): print(f"Вы ввели {a}") if __name__ == "__main__": t1 = Thread(target=task_1, args=(100,)) # Вводим параметры t2 = Thread(target=task_2, args=(818,)) # Вводим параметры t1.start() # Запускаем поток t2.start() # Запускаем поток t1.join() # Закрываем поток t2.join() # Закрываем поток

3. Multiprocessing

multiprocessing - библиотека, которая позволяет работать в многопроцессорном режиме.

В отличие от библиотеки threading, multiprocessing ускоряет работу Python кода, но в урон большего потребления ресурсов.

Почему многопроцессорность позволяет ускорить работу Python, а многопоточность нет ?

Когда вы запускаете многопроцессорный код, каждый процесс имеет свои ресурсы и свой GIL, именно из-за этого происходит большее потребление ресурсов.

Когда вы запускаете многопоточный код, все потоки скомкиваются в 1 GIL, где просто происходит переключение между ними.

Каждый процесс работает только с теми ресурсами, которые вы ему предоставите, например если вы передали в 1 процесс одни параметры а во 2 процесс другие, они смогут этими ресурсами обменяться только через канал, и именно поэтому если цель - параллельная работа, лучше использовать threading, чтобы не писать лишний код для обмена данных между процессами.

Приведу визуальный пример работы многопроцессорности

Asyncio, threading, multiprocessing — как пользоваться

Как вы видите, при запуска каждого процесса запускается свой GIL, что позволяет каждому процессу занимать весь GIL и ни с кем не делиться ;)

Тестим скорость работы в многопроцессорном режиме и однопроцессорном режиме -

код:

import datetime from multiprocessing import Process def test(a: int): while a > 0: a -= 1 if __name__ == "__main__": p = Process(target=test, args=(100000000,)) # Вводим параметры p2 = Process(target=test, args=(100000000,)) # Вводим параметры p3 = Process(target=test, args=(100000000,)) # Вводим параметры print("Старт на многопроцессорности: ", datetime.datetime.now().replace(microsecond=0)) p.start() # Запускаем процесс p2.start() # Запускаем процесс p3.start() # Запускаем процесс p.join() # Закрываем процесс p2.join() # Закрываем процесс p3.join() # Закрываем процесс print("Конец на многопроцессорности: ", datetime.datetime.now().replace(microsecond=0)) # print("Старт на одном процессе: ", datetime.datetime.now().replace(microsecond=0)) # test(100000000) # test(100000000) # test(100000000) # print("Конец на одном процессе: ", datetime.datetime.now().replace(microsecond=0))

результат:

Asyncio, threading, multiprocessing — как пользоваться

При запуске текущего кода на нескольких процессах - код работает в 3 раза быстрее!

Как обмениваться данными между процессами ?

Есть три известных способа Queue, JoinableQueue и SimpleQueue, о них подробно тут -

Мы рассмотрим только 1 - Queue:

from multiprocessing import Process, Queue def test(a: str, b: Queue): print(f"Вы ввели: {a}") b.put(a) def test_2(a: str, b: Queue): print(f"Вы ввели: {a} и {b.get()}") if __name__ == "__main__": queue = Queue() # Создаем канал p = Process(target=test, args=("Привет", queue,)) # Вводим параметры p2 = Process(target=test_2, args=("Пончик", queue,)) # Вводим параметры p.start() # Запускаем процесс p2.start() # Запускаем процесс p.join() # Закрываем процесс p2.join() # Закрываем процесс

Мы создаем канал queue и с помощью него берем данные из одного процесса и передаем в другой.

Если моя статья кому-то пригодилось или внесла ясность в голову, для меня это лучший подарок, но от комментариев, подписки и кармы я не откажусь ;)

Всем удачи!

5
Начать дискуссию