この記事は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さんっぽい文言ですが、その後に実際に彼のコメントが引用されています。
まず、こちらのstack overflowの「asyncioとtrioって何が違うの?curioってライブラリもあるそうなんだけど…」という質問に、trioの"primary author"でcurioの"contributor"でもあるNathaniel J. Smithさんが以下のように答えています。
- asyncioのほうが成熟したライブラリだ
- trioはコードをもっとシンプルにしてくれる
- 詳細は多くの違いがある
それでは、「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
こちらが今回の記事のほぼ元ネタです。この記事では説明し尽くせていない部分もあるので、見ておくといいと思います。
What is core difference between asyncio and trio
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のアイデアの元になった言語やライブラリが紹介されています。これは骨が折れそうですが、並列/並行処理全般の様々なアプローチを学べそうなので、徐々に読んでいこうと思っています。
Pythonをとりまく並行/非同期の話
並列・並行処理関連は情報が多くて最初は混乱すると思います。慣れてない方は「CPUプログラムのボトルネックとなる処理がCPUが原因なのか、IOが原因なのか」は重要なのでよく見ておきましょう。
async/awaitはIOが原因となる処理を高速化するための手法です。その後、Nathaniel J. Smithさんの動画を見ると分かりやすいと思います。
あまり関係ないですが、"GIL"のことを"ギル"って発音していたのでビックリしました。自分は"ジル"って読んでたので…。
trioを知る前は「Go, Erlang, Haskellとかの並列・並行処理が得意な(と言われている)言語のアプローチを勉強しようか」って思っていたんですが、もう少し楽できそうです。
最後に
公式リポジトリにもあるように、まだtrioは新しくてやや実験的なプロジェクト(young and still somewhat experimental)のようです。私はプライベートでの開発で試していこうと思います。
他にも、いくつかアドバイスや注意点が書かれています。例えば以下のようなものです。
- APIの後方互換性を破壊する変更はissue #1に投稿されるので購読してほしい
- 使ってみて分からない点はchat room, filing a bug, or posting a question on StackOverflowを通して聞いてくれ
早くtrioくんと平和な非同期処理の世界に暮らせる日を待ち望んでいます。
わーすた / うるとらみらくるくるふぁいなるアルティメットチョコびーむ MUSIC VIDEO Short Ver. - YouTube