12.13 Асинхронные возможности Python

Краткий пересказ теории

В действительности:

Синтаксический сахар и асинхронный протокол:

Asyncio

  1. Напишем программу сортировки слиянием массива из 16 элементов
    • Все 15 слияний делать руками с помощью вызова функции слить(начало1, конец1, начало2, конец2)

    • Для слияния используется общий глобальный второй массив
  2. {i} Перетащим их в структуру asyncio.run()/await 

    • Для наглядности снабдим каждый шаг сортировки await asyncio.sleep(0.1) и выводом номера «задания» (передадим его в качестве пятого параметра)

    • Разницы никакой, всё равно запуск последовательный

  3. Для придания асинхронности надо писать свой mainloop. Но он уже есть — это asyncio.run(). Не надо писать свой mainloop, надо пользоваться asyncio.create_task() и asyncio.gather(все 15 слияний)

    • Получается какая-то каша вместо сортировки, почему?
    • {i} Эшелонируем сортировку с помощью нескольких фрагментов вида create_task()… + gather(все_таски_из фрагмента) (палево — их пять)

  4. {i} Эшелонируем сортировку с помощью задания тайм-аута (в пятом параметре будем передавать номер эшелона n и ждать n/c секунд) и общего create_task() / gather()

    • Чем этот подход плох?
  5. Синхронизация

    • в частности события

      • await event.wait() перебрасывает в mainloop

      • вернётся, когда кто-нибудь сделает event.set()

    • Пример

  6. <!> Задача_1: переписать сортировку с использованием asyncio.Event для синхронизации

    • Каждая функция слияния работает так
      • Дожидается события конца слияния правой половины
      • Дожидается события конца слияния левой половины
      • Выполняет слияние
      • Выставляет готовность события конца слияния своего интервала
      • Внутри цикла слияния обязательно должен присутствовать asyncio.sleep(0) — иначе синхронизация не понадобится, и проверить, что она произошла, не получится

    • Замечания:
      • Часть слияний (те, что по одному элементу) не требуют предварительных других слияний, поэтому они либо это проверяют и не ждут, либо события, которые они ждут, уже помечены случившимися
      • События можно делать просто по именам (7 штук, типа событие_4_8) и передавать их в качестве параметров сопрограмме слияния

      • События можно положить в словарь и научить сопрограмму вычислять, какие именно элементы этого словаря — два входных события, а какой — событие готовности результата
      • Лично я поленился, и написал events = defaultdict(asyncio.Event), т. е. совместил два предыдущих варианта)

    • Решение при этом должно представлять собой один большой gather() на все созданные задачи

    • Формат ввода/вывода для Д/З:
      • Ввод: Строка в квадратных скобках из 16 чисел через запятую, пригодная для eval(input()) для типа list

        [22, 58, 95, 33, 47, 11, 76, 38, 70, 84, 40, 35, 56, 28, 13, 23]
      • Вывод: результат работы print(список) (со скобками и запятыми)

        [11, 13, 22, 23, 28, 33, 35, 38, 40, 47, 56, 58, 70, 76, 84, 95]

Д/З

  1. Задача_1: доделать, сделать тесты, не забыть закомментировать отладочный код)
  2. <!> Задача_2: (на дом). Переписать сортировку под произвольный (не слишком большой) объём данных

    • Условие: как в Задаче_1, за исключением того, что на вход подаётся список произвольной длины (обязательна синхронизация посредством Event и использование общего gather())

    • Тесты:
      1. Сортированный в обратном порядке список длины 16

      2. Случайный список длины 65
      3. Случайный список длины 63
      4. Случайный список длины 48
      5. Случайный список длины 1000

LecturesCMC/PythonIntro2021/Prac/12_Async (последним исправлял пользователь FrBrGeorge 2021-12-13 21:42:17)