この記事はPython その2 Advent Calendar 2018 の遅れてきた15日目の記事です。
最近、クローリング用のプログラムのasyncioを使った並行処理のプログラミングをしており、「asyncioのベストプラク ティス」という趣旨の記事を書こうと思っていたのですが、自分自身感覚を掴めておらず、「あれ?これってasyncioのAPI 自体も微妙なのでは?」「そもそもasyncioのドキュメント のドキュメントを読んでも、どの機能をどう組み合わせて使えばいいか分からない」と思ってしまっていて記事を書きかねていました。
ただ、知人から「trio というライブラリのAPI 設計が素晴らしい」と薦めて頂いたのと、あまりにも待たせてしまうのも申し訳ないのでひとまず「調べてみた」レベルでアウトプットを出します。
私はまだ並列/並行/非同期処理に詳しいわけではないので、大きな思い違いをしている可能性はあります。鵜呑みにせず、一緒に勉強しましょう。もし変な箇所があればコメント等で連絡ください。
trioとは
「async I/O for humans and snake people」を名乗るライブラリです。requestsなどのAPI 設計が優れたライブラリ作者のKenneth Reitzさんっぽい文言ですが、その後に実際に彼のコメントが引用されています。
github.com
まず、こちらのstack overflowの「asyncioとtrioって何が違うの?curio ってライブラリもあるそうなんだけど…」という質問に、trioの"primary author"でcurio の"contributor"でもあるNathaniel J. Smith さんが以下のように答えています。
asyncioのほうが成熟したライブラリだ
trioはコードをもっとシンプルにしてくれる
詳細は多くの違いがある
asyncioや関連ライブラリではsome_function
を実行したときにコルーチンが実は完了していない場合もあり、例外やタイムアウト 関連で厄介な問題が起きるが、trioの"nurseries"を使う方法ではその動作が完了していることを保証できる
trioのタイムアウト や中止の方法が今までにない新しいものである
asyncioはAPI 設計に冗長な部分や、そのための落とし穴が多い
それでは、「asyncioよりシンプルなAPI 」を実現するために、trioではどういう設計になっているのでしょうか?「Async concurrency for mere mortals - PyCon 2018 」を引用しつつ紹介します。
Python でasync/awaitを使ったコードを書く時、Python の同期的な関数の中に、asyncio等のライブラリでasync/awaitの関数を実行して…とプログラミングしていきます。trioのアーキテクチャ では更に"Nurseries"と"Cancel Scope"が追加されています。
asyncioには存在しない"Nurseries"の層では、async with trio.open_nursery()
ブロック中でstart_soon
でコルーチンをセットしてバックエンドで実行開始し、async with
ブロックが閉じるときにこれらのコルーチンが処理を完了させている設計になっています。
そうすると、"nursery"のブロックの中でエラーが出たときに、ブロックに例外が通知され適切に処理できることが保証できます。その途中でgoto文論争や構造化プログラミングの話も出ていますが、おそらく「どこで何の処理が行われているか分からないと、適切にエラー処理できない」という例として触れているだけのようです。
次に"Cancel Scope"の説明です。ネットワークを介した処理を行う時、次の3つのことが起こりえます。
成功
失敗
どちらでもなく、永久にハングアップする
ハングアップした場合に備えて「必ずタイムアウト 処理を書くべき」だとNathaniel J. Smithさんは主張しています。trioでは、async関数の中で「ブロック内の処理がn秒以内に終わらなかった場合はキャンセルする」ような機構が提供されているようです。
Design and internals によると、この辺りが基となったライブラリの一つのcurio との大きな違いのようなのですが、両方のAPI に精通しないと読めないですね…。この辺りのAPI 設計があまり理解できていないので、tutorialを見てみようと思ったのですが、運悪く「When things go wrong: timeouts, cancellation and exceptions in concurrent tasks 」はまだ未整備でした。
関連記事
参考にした記事や、「ここから学んでいけば良さそう」という記事をメモしておきます。
Trio talks
こちらが今回の記事のほぼ元ネタです。この記事では説明し尽くせていない部分もあるので、見ておくといいと思います。
github.com
VIDEO www.youtube.com
What is core difference between asyncio and trio
stackoverflow.com
Some thoughts on asynchronous api design in a post async/await world
curio とasyncioの異なるアプローチを比較した記事。先述したNathaniel J. Smithさんが書いています。
Some thoughts on asynchronous API design in a post-async/await world — njs blog
Reading list
trioのReading list。trioのアイデア の元になった言語やライブラリが紹介されています。これは骨が折れそうですが、並列/並行処理全般の様々なアプローチを学べそうなので、徐々に読んでいこうと思っています。
github.com
Python をとりまく並行/非同期の話
並列・並行処理関連は情報が多くて最初は混乱すると思います。慣れてない方は「CPUプログラムのボトルネック となる処理がCPUが原因なのか、IOが原因なのか」は重要なのでよく見ておきましょう。
tell-k.github.io
async/awaitはIOが原因となる処理を高速化するための手法です。その後、Nathaniel J. Smithさんの動画を見ると分かりやすいと思います。
あまり関係ないですが、"GIL"のことを"ギル"って発音していたのでビックリしました。自分は"ジル"って読んでたので…。
trioを知る前は「Go, Erlang , Haskell とかの並列・並行処理が得意な(と言われている)言語のアプローチを勉強しようか」って思っていたんですが、もう少し楽できそうです。
最後に
公式リポジトリ にもあるように、まだtrioは新しくてやや実験的なプロジェクト(young and still somewhat experimental)のようです。私はプライベートでの開発で試していこうと思います。
他にも、いくつかアドバイス や注意点が書かれています。例えば以下のようなものです。
早くtrioくんと平和な非同期処理の世界に暮らせる日を待ち望んでいます。
わーすた / うるとらみらくるくるふぁいなるアルティメットチョコびーむ MUSIC VIDEO Short Ver. - YouTube
asyncio : みんなご存じ標準ライブラリ。
trio : 今回紹介したライブラリです。
curio : trioが影響を受けたライブラリの一つ。公式リポジトリの紹介 によると、あるベンチマーク ではtrioの50%程度早いとか。
uvloop : asyncioのloopを"ultra fast"なものに置き換えてくれるライブラリ。今回は「使いやすいものを探す」って趣旨ですが一応触れておきます。