プログラミング応用b 第10回 『スレッド1』 |
【競合とクリティカルセクション】
さて,次にマルチスレッド処理で問題になる「競合」について紹介しよう。
List 3 と List 4 は,ケーキ屋のシミュレーションである。List 3 は,ケーキ屋を表す CakeShop
クラスで,店頭に置いてあるケーキの数を表す numOfCake フィールドと,それを初期化する
コンストラクタ(List 3-①)が定義されている。List 3-②の getNumOfCake( ) はケーキ
の在庫数を返すアクセッサである。
List 3 CakeShop.java, List 4 ShopSim.java
List 3-③ から始まる sellCake( ) メソッドは,ケーキを1個売る,という処理を行う。List 3-④
でケーキの在庫があるかを確認したうえで,売れたら List 3-⑥でケーキ数を1個減らし,
true を返す。在庫がなかった場合は,ケーキを売ることができないので, false を返す。なお,
List 3-⑤は例によって便宜的なスリープである。
List 4の前半は客を表す Customer クラスの定義である。Customer はスレッドクラスになっ
ている。対象となるケーキ屋を参照するための shop フィールドを設け,さらに客には識別番
号をつけたいので,最後に発行した識別番号を表すクラス変数id,客の識別番号 myid の2つの
フィールドを用意している(List 4-①)。
そして,コンストラクタで myid と shop フィールドを設定する。肝心の run( ) メソッドでは,
shop. sellCake( ) を呼び出してケーキ屋にケーキを売ってもらった結果を表示する。
List 4 の後半は,シミュレーションを行う ShopSim クラスが定義されている。List 4-④ で,
10個のケーキの在庫がある状態でケーキ屋オブジェクトを生成し,List 4-⑤ では,客スレ
ッドを15個生成・実行している。
List 3 と List 4 を同じプロジェクトに登録し,ShopSim クラスを実行すると,どうなるだろうか。
ふつうに考えれば,10個しかケーキが無いのだから,10人だけがケーキを買うのに成功し,あと
の5人はケーキを買えないはずである。しかし,実際に CakeSim を繰り返し実行してみると,
11人以上の客がケーキを買うことができる現象を確認できる。下図は実行した例のひとつで,
なんと10個しか無いケーキを15人の客全員が買うことに成功している。
なぜ,このようなことになったのだろうか。
【競合状態】
この問題が起こる様子(例)を,Fig.4 に示す。Fig.4 では,2つの Customer型スレッド a, b が
実行される様子を示している。
今,残りのケーキ数(Fig.4の右)が1個の状態で,a と b が sellCake( ) を呼び出すところから
考えてみよう。
まず,スレッドa が sellCake( ) を呼び出す(Fig.4-①)。つぎにスレッドb に処理が切り替わ
り,スレッドb が sellCake( ) を呼び出す(Fig.4-②)。ふたたびスレッドa に処理が移り,ケー
キの在庫があるかどうかを確認する(Fig.4-③)。このとき,在庫数(numOfCakeの値)は1で
あるから,在庫があることになる。さらにスレッドb に切り替わり,同様に在庫数の確認が行われるが,
この時点でもケーキの在庫数は1で,在庫が未だあることになってしまう。これが,11人以上が
ケーキを買うことができた原因である。スレッドb は続いてケーキの在庫数を1個減らし(Fig.4-⑤),
ケーキの在庫数は 0 になる。次に,スレッドa もケーキの在庫数を減らし(Fig.4-⑥),ケーキの
在庫数は -1 になってしまう。
問題をまとめると,あるスレッドが「在庫数の確認→在庫数の変更」という処理を行ってい
る間に,他のスレッドの処理が入り込んでしまうために,矛盾が生じている,というわけである。
このように,複数の並行処理がプログラムの整合性を破壊する状態を,競合状態(race condition)
と呼ぶ。
このプログラムが処理が正常に動作するには,「在庫数の確認→在庫数の変更」という処理
の間に,在庫数を参照したり,変更する他のスレッドが実行されてはいけないのである。
一般に,あるリソース(変数やオブジェクトなど)を「参照→変更」する間は,そのリソースを参照したり
変更する他の処理が並行で実行されてはならないのである。
このように,あるリソースに対して,他のコードが並行実行されてはいけないコード区間を
クリティカルセクション(critical section)と呼ぶ(Fig.4のスレッドaでは③から⑥の間)。
この競合を回避する方法が,次に紹介する重要な「同期(synchronization)」である。