PHPがJava(サーバで動くServlet)より品質面で優れている点を考えてみた。
PHPはマルチプロセス、Javaはマルチスレッドが前提になっている点ではないだろうか。
(Apache環境で動くPHPは、Apacheの設定によってはマルチスレッドで動かすことも可能だが、通常はマルチプロセスで動かす。IIS等のApache以外の環境で動くPHPは不明。ご存知の方がおられたらコメントください。)
スレッドセーフなプログラム(マルチスレッドで動作するプログラムで「マルチスレッドに起因する問題」が発生しないプログラム)を作成するためには、この問題を理解したプログラマが必須である。
では「マルチスレッドに起因する問題」とは、どのようなものであろうか。
久しぶりのブログでは、このことを解説したい。
1.プログラムとプロセス
プログラムとはファイルの一種で、実行形式(Windowsの*.exe等)ファイルや、実行形式ファイルから参照される動的ライブラリ(Windowsの*.dllやLinux系の*.so等)である。
プロセスとは「実行中のプログラム」のことである。
またプロセスが使用するメモリは、次の三種類の領域に大別される。(図-1参照)
(A)コード情報が格納されるコード領域
(B)データ情報が格納されるデータ領域
(C)制御情報が格納される制御領域
【図をクリックすると別ウィンドウまたは別タブで表示されます。(IEは別ウィンドウ、IE以外のマルチタブ型ブラウザは別タブです。)】
コード情報とは「プログラムにおける変数以外の情報(標準関数、自作関数、制御構造等)」のことで「プログラムがどのような処理を行っても変化しない固定情報」である。(当然のことであるが、コンパイル後のマシン語の情報である。)
データ情報とは「プログラムにおける変数」のことで「プログラムの処理に応じて変化する可変情報」である。(プログラムにおける定数は、コンパイラに依存するが「コード情報」とみなされるのではと思われる。)
制御情報とは「プロセスのプロパティ情報」や「現在実行中のコード情報」である。(プロセスのプロパティ情報とは「いつ、どのOSユーザーが、どのプログラムを実行したのか?等の情報」、現在実行中のコード情報とは「ソースコードのXXX行目に相当する命令を実行中、等の情報」である。)
2.マルチプロセス
ここまでの知識をもとに「サービス提供中のサーバの状態」を考えてみる。
サーバは、複数のクライアントに対して、同じサービスを同時に提供することがある。
例えば「一般的なLinux系のFTPサーバ」が「クライアントX、Y、Zの3クライアントに対して、同時にFTPサービス(ダウンロードやアップロード等)を提供中である」と仮定してみよう。
この場合、FTPサーバには「FTPサーバ用プログラム(ftpd等)を実行するプロセス」が3個存在している。(図-2参照)
【図をクリックすると別ウィンドウまたは別タブで表示されます。欠落箇所も表示されます。】
「一つのプログラムが、複数のクライアントに対して、同時にサービスを提供する」場合、「同じプログラムをもとにしたプロセスが、クライアント毎に存在する」ことを、ここではマルチプロセスと記す。(図-2のように、クライアントX用のプロセス、クライアントY用のプロセス、クライアントZ用のプロセスが同時に存在していることを、ここではマルチプロセスと記す。)
図-2のようなマルチプロセスの場合、FTPサービスを提供する3プロセスのコード領域は、全て同じ内容である。つまり「一つのプロセスで『複数クライアントからのリクエストを同時に処理』できれば、コード領域が一つで済み、メモリが節約できる」「プログラムをコード領域に読み込む時間も短縮できる」ため、サーバの負荷を軽減できるのである。
サーバの負荷を軽減するために考え出されたのが、マルチスレッドというものである。
3.マルチスレッドとその問題点
マルチスレッドとは「一つのプログラムが、複数のクライアントに対して、同時にサービスを提供する」場合、「一つのプロセスで、複数のクライアントからのリクエストを、同時に処理できる」ようにしたものである。「各クライアントからのリクエストを、個別に処理するプロセスのようなもの」がスレッドと呼ばれる。
また、マルチスレッドで稼働中のプロセスが使用するメモリは、次の五種類の領域に大別される。(図-3参照)
(A)全てのスレッドで共有されるコード情報が格納されるコード領域
(B)全てのスレッドで共有される共通データ情報が格納されるデータ領域
(C)全てのスレッドで共有される制御情報が格納される共通制御領域
(D)個別スレッド毎の個別データ情報が格納されるデータ領域
(E)個別スレッド毎の個別制御情報が格納されるデータ領域
【図をクリックすると別ウィンドウまたは別タブで表示されます。欠落箇所も表示されます。】
(C)と(E)はOSが制御するので、プログラマは意識しなくてもよい。
プログラマが注意すべきは、(B)と(D)だ。
クライアントからのリクエスト処理時、クライアント毎に異なる情報は、必ず(D)の領域に定義した変数(Javaならstaticでないローカル変数)で処理せねばならない。万一、(B)の領域に定義した変数で処理すると、とんでもないトラブルが発生する可能性がある。
例えば「ログイン直後に『氏名やサービス利用状況等の個人情報』が表示されるWebサービス」がスレッドセーフでないと仮定する。サーバ側プログラムは次の(ア)~(ウ)の機能を順に行うものとする。
(ア)ログイン認証を行う
(イ)認証OKの場合、個人情報をDBから検索し、結果を変数に格納する
(ウ)認証OKの場合、(イ)でセットされた変数から会員専用画面を編集表示する
上記(イ)の機能にバグがあり、DBの検索結果を本来であれば図-3(D)の領域の変数にセットすべきところを図-3(B)の領域の変数にセットしているとする。
このようなWebサービスに、田中さんと中村さんがほぼ同時にログインしたとしよう。
そしてサーバ側で次の(1)~(6)の順序で各機能が実行されたとする。(図-4参照)
(1)田中さんのログイン認証
(2)中村さんのログイン認証
(3)田中さんの個人情報をDBから検索し、結果を変数に格納する
(4)中村さんの個人情報をDBから検索し、結果を変数に格納する
(5)田中さん用の会員専用画面を編集表示する
(6)中村さん用の会員専用画面を編集表示する
このように処理されると、田中さんのログイン後画面には「中村さんの個人情報」が表示されるのである。
理由は(4)の処理で(B)の領域の変数が「中村さんの個人情報」で上書きされるからである。
【図をクリックすると別ウィンドウまたは別タブで表示されます。欠落箇所も表示されます。】
マルチスレッドの最大の問題は、テストでスレッドセーフであることを証明できないことだ。リクエストが集中しなければ問題は発生しないし、また、リクエストが集中しても問題が発生しないこともある。「リクエストを集中させる負荷テスト」で「スレッドセーフに作られているようだ」という気休めは得られるが、それはスレッドセーフであることの証明ではない。
この点において、PHPは確実にJavaより品質面で優れていると断言できる。
(性能面ではマルチスレッドのほうがマルチプロセスよりも優れているが、サーバ性能の向上で、その差はそれほど大きくないのではないだろうか。)
※メモリ関連の説明を行ったので、coreダンプの説明も参考までに。Linux系のシステムで異常が起きた時、coreダンプと呼ばれるファイルが出力されることがある。coreダンプとは、異常が起きた時にプロセスが使用していたメモリ情報(コード領域・データ領域・制御領域)及びCPU情報をファイルに出力したものである。coreダンプは、例外処理が実装されていないプログラムで「ゼロによる割り算」等の異常なコードが実行された場合に出力される。(PHPはスクリプトに例外処理がなくても、PHP本体に例外処理が備わっており、display_errorsに応じてエラー情報が出力される。coreダンプは出力されない。)
※この記事も含め、これより過去の記事は、はてなブログから引っ越ししてきたものです。上記で用いた図ですが、原寸大のものは2回間隔を置いてクリックして頂くと表示されます。