12.06 Асинхронные возможности Python
Что нужно из теории
Что такое и как работает yield from
Как поймать значение из return значение в генераторе (res = yield from генератьор или поле исключения StopIteration)
async def + return ≈ генератор
await ≈ yield from
Использование asyncio
Асинхронность в понимании asyncio:
- Образующий цикл не виден вообще
Сопрограмма — это обычный async def (то есть генератор-функция, но с дополнительными свойствами)
- Передача управления образующему циклу:
await asyncio.sleep(время) (в том числе 0)
А вот yield почти (или совсем) не используется
Сопрограммы нужно регистрировать в образующем цикле в виде заданий (.create_task())
Запуск образующего цикла: asyncio.run(сопрограмма)
Отличие await сопрограмма() от await asyncio.create_task(сопрограмма)
Запуск заданий с помощью .gather()
TODO непонятно ручная барьерная синхронизация:
напишите функцию squarer(), возвращающую квадрат параметра, и функцию doubler(), возвращающую удвоенное значение параметра
по заданному списку из двух чисел [x, y] получите список [2*x^2, 2*y^2], вызвав при помощи .gather() сначала две сопрограммы squarer() на элементах этого списка, потом на элементах списка-результата две сопрограммы doubler()
- выведите полученный в итоге список
// TODO ручная синхронизация: две сопрограммы возвращают .gather(), две других обрабатывают результат. Придумать правдоподобный пример
Использование событий
цепочка событий:
- напишите четыре функции:
snd() однократно отправляет событие evsnd
mid1() ожидает событие evsnd и после получения отправляет событие evmid1
mid2() аналогична mid1(), но отправляет событие evmid2
rcv() ожидает событий evmid1 и evmid2
каждая функция выводит после генерации события строку "<имя_функции>: generated <имя_события>", а после получения события - строку "<имя_функции>: received <имя_события>"
- запустите эти четыре функции как сопрограммы
- напишите четыре функции:
Использование очереди
перекладывание из очереди в очередь
- напишите три функции:
prod(): в цикле кладет в очередь q1 строку "value_<счетчик_цикла>", после каждой укладки ждет 1 секунду
mid(): в цикле ожидает получения элемента из очереди q1, кладет полученный элемент в очередь q2
cons(): в цикле ожидает получения элемента из очереди q2
цикл в функции prod() на 5 итераций, в остальных функциях - бесконечный
каждая функция после укладки значения выводит строку "<имя_функции>: put <значение> to <имя_очереди>", а после получения значения - строку "<имя_функции>: got <значение> from <имя_очереди>"
- запустите эти три функции как сопрограммы
- напишите три функции:
- Начать разбор второй задачи Д/З
Д/З
Задача_1: Перекладывание из очереди в очередь
- написать три сопрограммы:
writer(очередь, задержка) — каждые задержка секунд помещает в очередь строку номер_задержка, где номер растёт от 0 до бесконечности. При наступлении заранее заданного события завершает работу. Работа сопрограммы начинается с задержки.
stacker(очередь, стек) — бесконечно перекладывает содержимое очереди в стек. При наступлении заранее заданного события завершает работу.
reader(стек, количество, задержка) — снимает и выводит значение со стека каждые задержка секунд. После вывода количество значений посылает то самое событие и завершает работу. Работа сопрограммы начинается с задержки.
Считать через запятую четыре значения: задержка1, задержка2, задержка3, количество. Это, соответственно, задержки двух writer-ов и reader-а, и количество чтений.
In:
2,3,1,10
Out:
0_2 0_3 1_2 1_3 2_2 3_2 2_3 4_2 3_3 5_2
- написать три сопрограммы:
Задача_2: сортировка слиянием
написать сопрограмму «слияние двух соседних упорядоченных отрезков» merge(A, B, start, middle, finish, event_in1, event_in2, event_out)
ждёт наступления обоих _in-событий (признак того, что отрезки отсортированы)
объединяет два упорядоченных отрезка A[start…middle) и A[middle…finish) в один B[start…finish)
посылает событие event_out
Написать сопрограмму mtasks(A), в которой
Запланировать сначала объединение отрезков A единичной длины, помещая их в B, потом — отрезков B длины 2, помещая в A, потом длины 4 и т. д.
Должно получиться от N - 1 до N + log₂N заданий, зависимость между которыми задана событиями
Возвращается список заданий task, пригодный для одновременного запуска посредством asyncio.gather(*tasks)
In:
Out
37 6 5 3 5 1 8 3 7 9 3 9 2 6 9 1 8 3 0 6 3 4 8 8 6 8 5 4 9 7 3 6 9 4 0 1 1 2 3 3 3 3 3 3 4 4 4 5 5 5 6 6 6 6 6 7 7 8 8 8 8 8 9 9 9 9 9 True