「並行」と「並列」と「非同期」
面白い記事を見かけたので
今回はこの記事を起点につらつらと書いてみる。
「並行」と「並列」と「非同期」
プログラミングおよびシステム設計に於いて「並行」と「並列」は意味が違うよ,というのはよく言われるし,私も Go 言語関連の話で紹介したことがある。 その一方で「非同期」と「並行」は違うよね,という話は確かにあまり聞かない。 先程挙げた記事では
Asynchrony: the possibility for tasks to run out of order and still be correct.
Concurrency: the ability of a system to progress multiple tasks at a time, be it via parallelism or task switching.
Parallelism: the ability of a system to execute more than one task simultaneously at the physical level.
つまり「並行」と「並列」は能力(ability)だが「非同期」は実行順序の可能性(possibility)であると説明されている。 あるいは順序の自由度の問題と言い換えたらいいだろうか。
ここでプログラミング言語 Zig のコードが登場する。
たとえば Zig による非同期処理は io.async
を使って以下のように記述するそうなのだが
// assume that server.listen has already been called io.async(Server.accept, .{server, io}); io.async(Client.connect, .{client, io});
io.async
はシングルスレッドのブロッキングモードで動作するため,上のコード例の場合,一方の処理が他方の処理でブロックされてしまい並列処理にはならないらしい。
この挙動はユーザレベルスレッド1 の特性に近い(Zig の実装が実際にどうなっているかは知らないが)。
ユーザーレベルスレッドの主な利点は性能です。ユーザーレベルスレッドのコンテキストスイッチは、カーネルレベルスレッドのコンテキストスイッチよりも高速です。なぜなら、カーネルレベルのコンテキストスイッチでは、オペレーティングシステムが介入して次に実行するスレッドを選択する必要があるからです。カーネルを呼び出さずに実行を切り替えることができれば、実行中のプロセスは、キャッシュをフラッシュして処理速度を低下させる必要がなく、CPU を保持し続けられます。
ユーザーレベルスレッドを使うことの不都合な点は、ブロッキング I/O 呼び出しを行うコードを実行するときに現れます。ファイルからの読み込みが必要な状況を考えてみましょう。オペレーティングシステムはプロセスを 1 つの実行のスレッドと見なすため、ユーザーレベルスレッドがこのブロッキング読み込み呼び出しを実行すると、プロセス全体がスケジュールから外されます。同じプロセス内に他のユーザーレベルスレッドが存在する場合、読み込み操作が完了するまで、それらのスレッドは実行されません。
正しく並行処理として記述したいなら
try io.asyncConcurrent(Server.accept, .{server, io}); io.async(Client.connect, .{client, io});
という感じにコードで明示する必要があるそうな。
拙文「goroutine はグリーンスレッドではない」でも書いたが Go の goroutine はカーネルレベルスレッドとユーザレベルスレッドのハイブリッドになっていて,非同期かつ並行な処理を記述することが出来る。 なので非同期と並行を分離して考えることはないし,その必要もない(そのぶんランタイム側で制御するスケジューリングが複雑になるんだけど)。 強いて言うなら Go のメモリモデルについて注意が必要といったところか。
Zig のように非同期処理と平行処理を明示的に分離して記述するのは面白いと思う2。 より低レベルな処理に気を配らないといけないので,コード設計が面倒くさいっちゃあ面倒くさいんだけどね。
参考図書
- Go言語で学ぶ並行プログラミング 他言語にも適用できる原則とベストプラクティス impress top gearシリーズ
- James Cutajar (著), 柴田 芳樹 (著)
- インプレス 2024-12-04 (Release 2024-12-04)
- Kindle版
- B0DNYMMBBQ (ASIN)
- 評価
読書会のために購入。インプレス社の本は Kindle 版より版元で PDF 版を買うのがオススメ。「並行処理」について原理的な解説から丁寧に書かれている。 Go で解説されているが Go 以外の言語でも応用できる。
- Goならわかるシステムプログラミング 第2版
- 渋川よしき (著), ごっちん (イラスト)
- ラムダノート 2022-03-23
- 単行本(ソフトカバー)
- 4908686122 (ASIN), 9784908686122 (EAN), 4908686122 (ISBN)
- 評価
-
“Asynchrony is not Concurrency” ではユーザレベルスレッドをグリーンスレッドと呼んでいるように見える。なおグリーンスレッドは元々 Java の用語で,しかもバージョンごとに意味が変遷しているらしいので,この記事では「グリーンスレッド」という用語は使わないことにする。詳しくは拙文「goroutine はグリーンスレッドではない」を参考にどうぞ。 ↩︎
-
“Async Rust Is A Bad Language” では Rust における非同期処理の問題点が挙げられていて,最後に “Maybe Rust isn’t a good tool for massively concurrent, userspace software” とまで書かれていて笑ってしまった。みんな苦労してるんだなぁ。 ↩︎