構造化された並行性(こうぞうかされたへいこうせい、)は、プログラミング言語における並行処理、非同期処理のAPIのパラダイムのひとつで、呼び出した非同期処理ルーチンが完了するまで呼び出し元の特定のブロックが完了しないことを保証することをいう。構造化された並行処理(こうぞうかされたへいこうしょり)あるいは構造化並行性、構造化並行処理とも表される。構造化された並行性の「構造化」() の名は、1960年代に提唱されたプログラミング手法である構造化プログラミング () を引き継いでいる。 構造化された並行性は、非同期処理の開始と終了が明確に入れ子になることが非同期処理の構造化へのアプローチとなるとみなし、このことが明快さのみならずエラー処理などにおいて従来の手法より有利な点をもつとする。

概要

構造化された並行性の概念は2016年にマルティン・スーストリク () が非同期的に呼び出したルーチンをキャンセルする問題を例として定式化され、C言語のライブラリlibdillとして実装された。 2017年にはナサニエル・J・スミス () が非同期処理の実装の欠点を克服するものとして自身のPython言語の非同期処理ライブラリTrioに実装した。 翌2018年には、Kotlin言語のコルーチン・ライブラリを開発していたロマーン・エリザーロフ (, ) も同様のアイデアにたどり着いている。2021年にはSwift 5.5で言語機能として導入された。 Java、Scala、その他の言語のライブラリでもこの概念に基づく実装がある。

非同期処理を用いたプログラム開発に対応する言語やライブラリにおいて非同期処理の呼び出しは、しばしばその後の監視や再結合なしに行うことができ、プログラム構文上でのフローの把握の難しさや例外処理の困難さといった固有の問題が生じている。 スミスは、こうした問題を構造化プログラミングが提唱されたとき問題とされた制御フローのスパゲッティ化と対比し、非同期処理の呼び出し(Erlang言語のspawn、Go言語のgoなど)、またイベント発生などで開始されるコールバックを用いた処理、さらにpromiseやfutureと呼ばれる遅延された処理の一部が、かつてのgoto文と同様の問題を引き起こしているとした。

エドガー・ダイクストラらがあげたgoto文などの問題は、プログラムの一部のみを見ただけでは制御フローを把握できないというプログラマの推論の抽象化を破壊したことにあった。これに対して構造化された制御構文は、その始まりと終わりを明確に集約するという適切な制約を課すことで入れ子状にブラックボックス化できるようにし、この問題を解決した。 構造化された並行性では、非同期処理の呼び出し元の特殊なブロックの完了が、それが呼び出した非同期処理の完了を待つという単純な制約を新たに課すことによって、再度ブラックボックス化を可能としプログラマの推論を容易なものとするとする。このブロックは、タスクグループとも呼ばれる。

さらに、非同期処理タスクがタスクグループのブロック内に制約されることは、リソースの管理や例外処理においても利点をもたらす。処理の範囲がブロックとして明確であることにより、そこで必要なリソースの確保と解放は通常の逐次処理と同じように自動的に機能させられる。非同期タスクでの例外も逐次処理のようにブロックで捉えることができ、このときには原則として他のタスクも意味を失うものとしてキャンセルする実装が多い。キャンセルが必要な場合には、ブロック内で起動したすべての並行処理タスクとその子孫をキャンセルしてから完了を待つため、この場合もブロック外ではタスクは残らない。処理のタイムアウトもキャンセルの一形態として同様に処理される。

対応状況 構造化された並行性の概念はその有用性が認められ、2018年以降、各種言語の言語仕様あるいは公式ライブラリの仕様として相次いで取り入れられるようになった。

KotlinKotlinでは、公式のコルーチン・ライブラリ (kotlinx.coroutines) として構造化された並行性の概念を2018年のKotlin 1.3において取り入れた。このライブラリでは、コルーチン・スコープ (CoroutineScope) がタスクグループに相当し、非同期コルーチンはlaunch、asyncなどによって起動される。コルーチン・スコープはライフタイムを持つオブジェクトに対応して定義でき、そのライフタイムに応じてコルーチンの管理が行われる。 Swift2021年のSwift 5.5より標準言語仕様の一部として構造化された並行性が組み込まれた。withTaskGroup関数によって作られるTaskGroupブロック内でaddTaskを呼ぶことで非同期タスクが起動される。 PythonTrioの構造化された並行性のアイデアは、Python 3.11よりasyncio.TaskGroupとしてasyncio標準ライブラリに取り入れられた。あわせてasyncio.timeout()もタイムアウトの構造化された処理として導入されている。 JavaJavaにおいては、JDK19で実験的機能として導入され、2026年現在、JDK27でAPIのプレビュー段階にある。

例

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 を用いたナーセリー(、託児所・保育所の意)とよばれるブロック内でのみ起動できる。 start_soon() で起動したタスクは並行して実行され、両者が終了した時点でナーセリーから抜けることになり、プログラマはナーセリーの外でこれらの非同期タスクの心配をする必要はなくなる。

Kotlinのコルーチン

suspend fun f() { doSomethingFirst() coroutineScope { launch { doSomethingConcurrently2() } doSomethingConcurrently1()

doSomethingLast() }

Kotlinのコルーチンの場合、コルーチン内で coroutineScope() を呼ぶと、現在のコルーチンを一旦停止(サスペンド)し、新たな「コルーチン・スコープ」を作成する。 このスコープ内で launch() によって新たなコルーチンが起動されると、 launch() 内と、コルーチン起動後のスコープのコードは並行的に実行される。coroutineScope() を抜けた時点で、両者のコードが完了していることが保証される。

出典

関連項目 構造化プログラミング