Python and corutines
Nikolay, [13.02.17 13:12]когда питон в рантайме дергает функцию - для этой функции в памяти создается фрейм, в котором уже создаются объекты локальных переменных и всего такого
Nikolay, [13.02.17 13:13]
обычно, когда функция завершает выполнение и возвращает управление через return - фрейм становится ненужен и подбирается GC
Nikolay, [13.02.17 13:14]
но если она возвращает его через yield - фрейм остается в памяти и сохраняет полное текущее состояние функции, чтобы при следующем обращении к объекту этой функции продолжить выполнение с этого законсервированного состояния
Nikolay, [13.02.17 13:16]
вот, дальше - в питоне корутины реализованы через генераторы, но yield - оператор не только передачи контекста, но и приема значения, то есть можно продолжить выполнение функции, передав ей внутрь что-нибудь еще
Nikolay, [13.02.17 13:16]
именно это позволяет снаружи управлять цепочкой этих корутин, которые передают друг другу значения, явно переключая контекст
Nikolay, [13.02.17 13:17]
это и есть то, что называется кооперативной многозадачностью - когда каждый вычислитель делает свою работу и явно передает контекст дальше
Nikolay, [13.02.17 13:18]
ближе к питону - есть две концепции - корутины и эвентлуп
Nikolay, [13.02.17 13:18]
корутин в питоне сейчас два варианта - классические через генератор и yield и “новые” через async/await, которые по сути то же самое, но в красивой обертке и не совсем обратно совместимые (в 3.6 совместимость почти поправили)
Nikolay, [13.02.17 13:20]
эвентлуп же позволяет связать получение событий от системного вызова с обработкой этого события корутиной, то есть мы висим, скажем, на epoll или на файловом хэндлере, получаем от него события и в лупе в зависимости от вида события дергается разная корутина
Nikolay, [13.02.17 13:21]
получается, что выстраивается дерево - в лупе обработка передается корутине, та, в свою очередь, может дернуть еще несколько корутин (раньше - через yield from, сейчас - через await), и так далее.
Nikolay, [13.02.17 13:22]
поэтому async - это просто оператор, показывающий, что функция является корутиной, а await - оператор ожидания переключения контекста обратно из корутины
Nikolay, [13.02.17 13:23]
фишка в том, что при таком подходе все происходит в одном треде и нам не нужны в явном виде примитивы синхронизации, поскольку контекст переключается явно и параллельного обращения к чему-либо нет в принципе
Nikolay, [13.02.17 13:25]
но поскольку все зависит от лупа - он может переключаться с корутины на корутину, пока какая-то операция висит на await, что позволяет не блокировать все нафиг, а просто не ждать результата честно, пока промис (await) его не вернет, а тупо идти дальше.
Nikolay, [13.02.17 13:27]
это позволяет, в идеальном мире, сделать так, что выполнение программы блокируется только на внешних вызовах, например, непосредственно операции чтения из сокета или файла. То есть стало возможно, скажем, дернуть subprocess и читать его вывод по мере поступления, асинхронно, делая что-то с ним в то же время, а не как во втором питоне
Nikolay, [13.02.17 13:27]
то есть система сама нам присылает событие, когда надо что-то сделать, и наш код - просто коллбэк на это событие
Nikolay, [13.02.17 13:28]
соответственно, callback hell - это когда колбэков дохрена и они друг друга все дергают и в этом фиг разберешься, но с async/await хоть стало проще понимать, что к чему
Nikolay, [13.02.17 13:34]
ну, работа с корутинами - дело довольно простое. В первую очередь надо создать луп, потом - дерево корутин, которые друг друга ждут через await. И потом вершину дерева через asyncio.ensure_future() запихиваешь в луп и ждешь ее выполнения через loop.run_until_complete()
Nikolay, [13.02.17 13:34]
если у тебя там бесконечный цикл - тогда можно использовать loop.run_forever()
Nikolay, [13.02.17 13:35]
если же хочешь несколько корутин и несколько деревьев ждать сразу - тогда пихаешь все корутины в список и на него делаешь asyncio.gather()
Nikolay, [13.02.17 13:35]
тогда управление вернется только когда все корутины в списке закончат работу