メニーコア時代のプログラミングモデルを考える(その1)~共有ステート並列
現状で一番身近なマルチスレッドプログラミングといえば「共有ステート並列」(Shared State Concurrency)だ。C++やJavaで標準的に利用されているメソッドで、データのアクセス権を、全てのスレッドから共有された状態変数で制御しながら複数のスレッドを動作させる仕組みだ。この場合、プログラマが全てのスレッドがどのようにデータをアクセスするか、複数スレッドがどういった振る舞いをするかをあらかじめ綿密に設計しておく必要があり、小規模なプロジェクトであればまだしも、大規模なソフトウェア開発においては難易度が高い。設計をミスすると互いのスレッドが使用したいデータの解放をお見合いしながら待ち続けるデッドロック状態に陥ったりしてしまい、しかも、マルチスレッドプログラムの正当性はコンパイル時には検証できない。TIM SWEENEY氏も「共有ステート並列の仕組みで200万行のプログラムを書くのは困難だ。メニーコア時代においてはマルチスレッドに対応した上級プログラミング環境では、デッドロックのような問題はローレベルで解決されるべきだ」と述べる。
データのロックを忘れてしまった場合はデータの不整合やデータ競合も発生する。データへのアクセスタイミングが毎回、実行によって異なってくるため、バグが発生したときにはデバグが非常にやりにくいという問題もある。
「ゲームプログラムというのは、思うに、マルチスレッド実装するには最悪のテーマなんです。だから難しい」(TIM SWEENEY氏)
ゲームプログラムは、プレイヤー制御、敵制御、AI更新、エフェクト処理……といった様々なオブジェクトを表示1フレームの間に全てこなさなければならない。言ってみれば数千のオブジェクト更新を1/60秒間にすまさなければならず、これを共有ステート並列型マルチスレッド実装で、数十人のプログラマで書くのは難しい。
「なぜならば、全てのデータ更新の振る舞いを把握できている人間が誰1人存在しないからだ。プロジェクトに参加しているプログラマ達はプログラマであってコンピュータサイエンティストではない!」(TIM SWEENEY氏)
メニーコア時代のプログラミングモデルを考える(その2)~メッセージパッシング
各スレッドのプログラムは、自身のタイミングでオブジェクトの更新を行うようにプログラムするが、実際のオブジェクト更新はメッセージキューに積まれ、やはりマルチスレッド実装されたメッセージ処理部がそのメッセージに従ったそのオブジェクトの更新を行う。他のオブジェクトの更新は行わないことを鉄則とし、他のオブジェクトからのデータの参照もこのメッセージ伝達の規則に従って行うことで、データ競合や不整合を極力回避する。ロストプラネットのエンジンとして知られるMTフレームワークはこの実装に近い。このメッセージパッシング型の仕組みはアプリケーションの移植性が高い特長もある。
ただ、ゲーム内時間における同時刻の複数オブジェクトによる同一オブジェクトへの相互干渉などにおいてうまく同期をとらないとつじつまが合わなくなる。例えば2つのキャラクタAとBが同一の障害物とゲーム内同一時刻に衝突した場合の処理をどうするのか……といった問題だ。
TIM SWEENEY氏は「究極的には同一の複数データの更新を同期処理無しで実行可能にすることが理想だ」と述べている。