構造化された並行性(こうぞうかされたへいこうせい、英: structured concurrency)は、プログラミング言語における並行処理、非同期処理のAPIのパラダイムのひとつで、呼び出した非同期処理ルーチンが完了するまで呼び出し元の特定のブロックが完了しないことを保証することをいう。
構造化された並行性の「構造化」(structured) の名は、1960年代に提唱されたプログラミング手法である構造化プログラミング (structured programming) を引き継いでいる。
構造化された並行性は、非同期処理の開始と終了が明確に入れ子になることが非同期処理の構造化へのアプローチとなるとみなし、このことが明快さのみならずエラー処理などにおいて従来の手法より有利な点をもつとする。
概要
構造化された並行性の概念は2016年にマルティン・スーストリク (Martin Sústrik) が非同期的に呼び出したルーチンをキャンセルする問題を例として定式化され[1]、C言語のライブラリlibdillとして実装された[2]。
2018年にはナサニエル・J・スミス (Nathaniel J. Smith) が非同期処理の実装の欠点を克服するものとして自身のPython言語の非同期処理ライブラリTrioに実装した[3]。
同じ年さらにKotlin言語のコルーチン・ライブラリを開発していたロマーン・エリザーロフ (Роман Елизаров, Roman Elizarov) も同様のアイデアにたどり着いている[4][5]。2021年にはSwift 5.5で言語機能として導入された[6]。
Java[7]、Scala、その他の言語のライブラリでもこの概念に基づく実装がある。
非同期処理を用いたプログラム開発に対応する言語やライブラリにおいて非同期処理の呼び出しは、しばしばその後の監視や再結合なしに行うことができ、プログラム構文上でのフローの把握の難しさや例外処理の困難さといった固有の問題が生じている。
スミスは、こうした問題を構造化プログラミングが提唱されたとき問題とされた制御フローのスパゲッティ化と対比し、非同期処理の呼び出し(Erlang言語のspawn、Go言語のgoなど)、またイベント発生などで開始されるコールバックを用いた処理、さらにpromiseやfutureと呼ばれる遅延された処理の一部が、かつてのgoto文と同様の問題を引き起こしているとした[3]。
エドガー・ダイクストラらがあげたgoto文などの問題は、プログラムの一部のみを見ただけでは制御フローを把握できないというプログラマの推論の抽象化を破壊したことにあった。これに対して構造化された制御構文は、その始まりと終わりを明確に集約するという適切な制約を課すことで入れ子状にブラックボックス化できるようにし、この問題を解決した。
構造化された並行性では、非同期処理の呼び出し元の完了が、それが呼び出した非同期処理の完了を待つという単純な制約を新たに課すことによって、再度ブラックボックス化を可能としプログラマの推論を容易なものとするとする。
さらに、処理が入れ子であることが保証されることによって、リソースの自動解放や例外処理も通常の逐次処理と同じように機能させられ、キャンセルやタイムアウトへの対処も容易だとする[3]。
例
PythonのTrioライブラリ
async def f():
do_something_first()
async with trio.open_nursery() as nursery:
nursery.start_soon(do_something_concurrently1)
nursery.start_soon(do_something_concurrently2)
do_something_last()
Pythonの非同期処理ライブラリasyncioの代替となるべく開発されたTrioでは、並行処理を行うタスクは async with
を用いたナーセリー(nursery、託児所・保育所の意)とよばれるブロック内でのみ起動できる。
start_soon()
で起動したタスクは並行して実行され、両者が終了した時点でナーセリーから抜けることになり、プログラマはナーセリーの外でこれらの非同期タスクの心配をする必要はなくなる[3]。
Kotlinのコルーチン
suspend fun f() {
doSomethingFirst()
coroutineScope {
launch { doSomethingConcurrently2() }
doSomethingConcurrently1()
}
doSomethingLast()
}
Kotlinのコルーチンの場合、コルーチン内で coroutineScope()
を呼ぶと、現在のコルーチンを一旦停止(サスペンド)し、新たな「コルーチン・スコープ」を作成する。
このスコープ内で launch()
によって新たなコルーチンが起動されると、
launch()
内と、コルーチン起動後のスコープのコードは並行的に実行される。coroutineScope()
を抜けた時点で、両者のコードが完了していることが保証される[8]。
出典
関連項目