ノンブロッキングI/Oプログラミング
マルチコア対応をしつつ、ノンブロッキングI/Oプログラミングを行なう方法を考えてみる。 Spring WebFluxを勉強中だが、これまでSpringを使っていなかったのでなかなか進まない。 また、適切な教科書が見つからない。(現在、英語版のテキストを取り寄せ中。日本語版はない模様。) WebFluxの勉強が遅れているのもあるが、昔ながらの古いJavaで考えることにする。
スレッドプールの活用
前セクションで、マルチコアにおけるスレッドをアトミックな処理でスレッドセーフを実現する方法を学んだ。 アトミック性が確保できなければ同期処理を実装することになるが、うまい方法を考えてみたい。 様々な処理(これからプロセスと呼ぶことにする)を一つのスレッドに埋め込み、マルチスレッドを行なうことを考える。
図2-1は、スレッドセーフなスレッド処理をどのように実現するかを示したものである。 スレッド数はCPUのコア数に依存し、CPUの許容スレッド数を超えない範囲で設定し、スレッドプールを用意する。 スレッド k用のキュー kを各スレッドに用意し、FIFOで処理する。 共有メモリを扱う処理の場合、極力アトミックな処理を活用し、スレッドセーフを実現する。
各スレッドの実行はOSに依存し、どのコアCPUに割り当てられるかわからない。 また、いつスレッドの切り替え(コンテキストスイッチ)が起きるか全くわからない。 それゆえ、それぞれのスレッドが一つのコアCPUに張り付いて動作する補償はまったくないことになる。 OSのアップデートや各サービスの定期実行が発生すると、いつでもスレッドの切り替えが起きる。 そしてそれはJVMでは全く管理できない事象になる。
どのタイミングでスレッドが中断されるか全く不明という前提でプログラミングすることは重要だ。 これから作成する予定のプログラムは、一つのコアCPUに張り付いて動作することを前提に考えているが、 次の瞬間、別のコアCPUで動作することもあるだろう。スレッドセーフが特に重要になってくる。 コンテキストスイッチが頻繁に起きると処理効率が悪化する。 そのことに留意しながら、マルチコアプログラミングを考えることにする。
図2-1に示したように、スレッドはさらに細かなプロセスと言う処理単位に分割され、 キューはプロセス番号とヒープ領域にある処理すべきデータのポインタの情報を持つ。 キューはFIFOで一つずつ処理され、指定されたプロセスが実行される。 (Javaにはポインタの概念は無いが、Java自身ポインタの塊であり、オブジェクトの受け渡しはポインタ渡しと同じと考えてよい。)
各プロセスはノンブロッキングで処理され、ブロッキング要素は一切含まれない。そのため、高速処理が約束される。 しかしながら、ファイルI/Oや通信処理などのブロッキング処理もシステム全体でサポートする必要があり、どのように実現するかが重要な問題である。 たとえば、長時間かかるデータ検索を行なうとそれがボトルネックになってシステム全体のスループットを悪化させる。 他のデータベースサーバの活用など、その都度、問題に対処することになるだろう。
図2-2は、如何にしてノンブロッキングプロセスを構築するかを示したものである。
とある処理を考える。 具体的な処理は何も考えていないが、その中には単独のブロッキング処理とループ構造のブロッキング処理があるとしよう。 ブロッキング処理、具体的にはファイルI/Oや通信、または、後回しにしてもよい時間のかかる処理などがあげられる。
とある処理プログラムの流れを追っていき、ブロッキング処理に出会ったら、それまでの処理をプロセス化していく。 処理の断片をプロセス化するので、何をするプロセスなのかわかりにくくなってしまうが、その問題はいまのところスルーすることにする。 プロセスはすべてブロッキング要素のない状態で処理されるが、ブロッキング処理の結果が必要になれば、結果が帰るまで待ち状態になる。 つまり、ブロッキング処理に出会ったら、プロセスはいったん終了し、後述予定の待ちデータプール(休眠プロセスプール)に入れられる。
スレッドプールとコントロールプログラム
図2-3はスレッドプールとコントロールプログラムおよびその他の必要と思われるプログラム群を図式化したものである。 まだ抜けている部分もあるが、実際のコーディングが進むにつれて、より複雑化するに違いない。 とりあえず、この状態で各処理を明確にしておくことは重要だ。
今後、様々な機能を追加する段階で、互いに齟齬が生まれないようにするためにも、今の段階で明確な定義付けを行なっておく。 まあ、作っているうちに仕様変更が頻繁に起き、いつのまにか別のものになっていたなんてざらだが・・・
コントロールプログラムとスレッドプールのスレッドは常に動き続けるので、それぞれ別のコアCPUに張り付いて動くことを期待している。 その他のプログラムもスレッドとして動作するが、頻繁に動作しないことを想定している。 それゆえ、シングルコアのスレッドのように頻繁にコンテキストスイッチで切り替わるかもしれない。 しかしながら、想定したようにマルチコアで動作する保証は全くない。 スレッドプールのスレッドが同じコアCPUで動作したりする可能性もあるだろう。 まあ、なんとなく想定に近ければそれでよしとしたい。
現時点において、tomcatのWebsocketを使うことを想定している。 tomcatはマルチスレッドをベースに開発されてきたので、ノンブロッキングI/Oがどこまで使えるのか不安がある。 まあ、作ってみてダメなら、Spring Webfluxに切り替えることにしよう。
作成日: 更新日: