プログラミング応⽤b第9回 -...
Post on 26-Jun-2020
0 Views
Preview:
TRANSCRIPT
Web資料 : http://www.ohshiro.tuis.ac.jp/~ohshiro/progaa/村上クラス : http://www.edu.tuis.ac.jp/~ym206508/lecture/programming/
村上クラス令和元年11⽉20⽇
プログラミング応⽤b 第9回
スレッド2
今回のトピックは
• デッドロック問題とその解決法
• 待機と待ち合わせを使って複数のスレッドを協調動作させる⽅法
の2つである。
本⽇の⽬標
前回の復習5. 同期処理この競合を回避する⽅法が,次に紹介する重要な「同期(synchronization)」である。
同期処理
Javaでは同期コード (synchronized code) という考え⽅で,この仕組みを実現している
競合状態を解消するために必要なプログラミング⽅法
あるリソース (変数など) に対して互いに競合関係にあるコード区間を指定する機能
指定されたコード区間をあるスレッドが実⾏している間は,他のスレッドは競合関係にあるコード区間を実⾏できないようにする
⾔い換えれば,指定されたコード区間を排他的に実⾏する( 排他処理 とか 同期処理 と呼ぶ)
現在、別のスレッドが使⽤しています︕
競合状態を解消するためには ...
同期コードによる排他処理の考え⽅あるリソース(r) に関して互いに競合関係にあるコード区間 A と B の間で,「早い者勝ち」という紳⼠協定を結んだコード区間を 同期コード と呼ぶ。
リソース (r)
同じリソース(r)をめぐって競合関係にあるので、「早い者勝ち」という紳⼠協定を結びましょう。
クリティカルセクションを含むコードA
クリティカルセクションを含むコードB
クリティカルセクション (Critical section)とは、計算機上において、単⼀のリソースに対して、複数の処理が同時期に実⾏されると「破綻」をきたす部分。
つまり、他のコードが並⾏実⾏されてはいけないコード区間のこと。
同期コードクリティカルセクションを含むコード区間は,同じリソースをめぐって競合状態にあることを⽰すために,共通のオブジェクトを⽬印として指定する。このオブジェクトは,リソースrと無関係でもかまわない。
リソース (r)
クリティカルセクションを含むコードA
クリティカルセクションを含むコードB
⽬印としての(ロック⽤)オブジェクトの指定
同期コード
⽬印⽤オブジェクトには,実⾏の 優先権 を表す「錠(ロック, Lock)」を持っていてもらう。そのため,⽬印⽤オブジェクトを,「ロック⽤オブジェクト」と呼ぶことにする。
⽬印としての(ロック⽤)オブジェクトの指定
錠 (ロック, Lock)
優先権
同期コード1. 紳⼠協定を結んだコード区間 = 同期コード 部分が実⾏されると,⾃動的に⽬印である
ロック⽤オブジェクトに「ロック」を要求する。
クリティカルセクションを含むコードA
クリティカルセクションを含むコードB
① ロックの要求
優先権
ロックは⼀個しかない!
同期コード2. ロック⽤オブジェクトがロック(実⾏優先権)を持っていれば,その同期コードはロックを
獲得できる。ロックを獲得できた同期コードは, 同期コードとして指定された区間が実⾏できる。
クリティカルセクションを含むコードA
クリティカルセクションを含むコードB
② ロックの獲得
• ⽬印 (ロック⽤オブジェクト)に ロックがあれば、それをを獲得できる︕
待機実⾏
ロック(優先権)を持っている⽅だけが クリティカルセクション を実⾏できる
優先権
• 他のコードがロックを既に獲得済みならば、その獲得は失敗し、それが解放されるまで⼀時停⽌状態で待つ。
同期コード3. ロックを獲得した同期コード部分が終了すると,その同期コードはロックを⾃動的に「解
放」し,ロックはロック⽤オブジェクトに返還。
クリティカルセクションを含むコードA
クリティカルセクションを含むコードB
待機終了
③ 解放
優先権
ロックが解放されると,同じロック⽤オブジェクトを指定している停⽌状態の同期コードの中からひとつを選んで,ロックを与え,実⾏を再開させる
同期コードの指定⽅法は2つ
② synchronized メソッド(メソッドをまるごと同期コードにする)
① synchronized ⽂(メソッドの⼀部を同期コードにする)
なお,同期メソッドをオーバライドしたメソッドは,同期メソッドではなく普通のメソッドになる。 オーバライド版のメソッドを同期メソッドにしたい場合は,明⽰的に同期メソッドとして定義する必要がある。
1. デッドロック
デッドロック
• 例えば,ロックを永遠に取得したままの同期コードがある場合,そのロックの解放を待つ別の同期コードは,永遠に停⽌したままになってしまう。このような状況をスターべーション(starvation,「餓死」という意味)と呼ぶ。
• スターべーションのもっとも発⽣しやすくもっとも深刻な状況が,デッドロック(dead lock)である。デッドロックは,複数のロックを取得する必要がある場合に発⽣する可能性がある。デッドロックが起こると,複数のスレッドが永遠に停⽌したままになる。
同期は競合を防ぐ合理的で便利な仕組みであるが,使い⽅を誤るとスレッドを永遠に停⽌させてしまう!!
デッドロックの⾝近な例紙が⼀枚,鉛筆が⼀本有るとする。今,2⼈の⼈物・太郎君と花⼦さんに「その鉛筆でその紙に,"こんにちは"と書け。」と同時に命令したとする。太郎君が最初に紙を⼿にし,花⼦さんが鉛筆を⼿にした。⇨ 太郎君には鉛筆が⾜りないし,花⼦さんには紙が⾜りない。結局,2⼈とも相⼿が⾃分が必要としているものを⼿放すまで⽬的を達することができずに⽌まってしまう!!
太郎 花子
例: ある銀⾏⼝座から別の銀⾏⼝座に処理を⾏うプログラム
3つのフィールドの初期化accountNumMax ... 現在開設されている⼝座番号の最⼤値accountNum ... ⼝座番号balance ... 残⾼
3つのフィールの値を設定するコンストラクタ
同期アクセッサ
⼝座クラス: Account.java
デッドロックを起こす送⾦トランザクションクラス: TransferTransaction.java
「取引」処理というような意味で,コンピュータ業界では,要求に応えて⾏う⼀件⼀件の処理のことを表す。
3つのフィールドの初期化source ... 送⾦元⼝座dest ... 送⾦先⼝座amount ... 送⾦⾦額
3つのフィールの値を設定するコンストラクタ
送⾦を⾏う部分
transfer()メソッドの呼び出し、送⾦を実⾏する。
• 送⾦元⼝座オブジェクト• 送⾦先⼝座オブジェクトの両⽅を変更する必要がある。
送⾦を⾏う部分
送⾦先⼝座オブジェクトdest
送⾦元⼝座オブジェクトsouce
送⾦⾦額送⾦
送⾦⾦額
⼝座残⾼を減らす ⼝座残⾼を増やす
送⾦処理
ロック要求 ①
ロック要求 ②
2重にロック⽤オブジェクトに指定し,その中で送⾦処理を⾏っている
トランザクションシステムクラス: TransactionSystem.java
送⾦元⼝座オブジェクトa1 と送⾦先⼝座オブジェクトa2 を⽣成
a1からa2への送⾦、a2からa1の送⾦トランザクションスレッドを⽣
成して、それぞれ実⾏を開始
⽣成・初期化されたTransferTransaction型
のオブジェクト
⽣成・初期化されたTransferTransaction型
のオブジェクト
run()メソッド内の同期コードのはじめで、1. sourceにロック要求2. 次にdestにロック要求つまり、1. a1 にロック要求2. 次に a2 にロック要求しようとする
run()メソッド内の同期コードのはじめで、1. sourceにロック要求2. 次にdestにロック要求つまり、1. a2 にロック要求2. 次に a1 にロック要求しようとする
⼝座クラス: Account.java デッドロックを起こす送⾦トランザクションクラス: TransferTransaction.java
トランザクションシステムクラス: TransactionSystem.java
実⾏すると、デッドロックが発⽣して 2つのスレッド は停⽌してしまう(そのためプログラムも終了しない)。なぜであろうか?
デッドロックが発⽣する様⼦ここに⽰すスレッドの切り替わり順はあくまでも⼀例であることに注意してください。実際実⾏するごとに異なる切り替わり⽅をします。
⑤ a2 のロックはすでにスレッド b の同期コードによって取得されているので,スレッド a の同期コードは a2 のロックの解放を待つために停⽌する。
⑥ 処理がスレッド b にうつって, dest(つまり a1)のロックを取得しようとする。しかし,ここでも a1 のロックはすでにスレッド a の同期コードによって取得されているため,スレッド b の同期コードは a1 のロックの解放を待つために停⽌する。
つまり,2つのスレッドの同期コードがお互いに,相⼿が保持しているロックの解放を待つために,停⽌しているのだが,両⽅とも実⾏が停⽌しているので,ロックを解放することはない。その結果,これら2つのスレッドは永遠に停⽌することになってしまうのである。
2. デッドロックの回避
順序づけによるデッドロック回避デッドロックは,複数の同期コードで,複数のロック⽤オブジェクトから
ロックを取得するとき,あべこべの順番でロックを取得するために発⽣する
花子太郎
先ほどの太郎と花⼦さんの問題を解決するには,命令に「必ず紙から⼿に取り,次に鉛筆を⼿に取ること」と付け加えればよい。そうすれば,太郎君が最初に紙を⼿にした後で,花⼦さんは太郎君が紙を⼿放すまで待つことになる。太郎君が紙の次に鉛筆を取って紙に「こんにちは」と書いて,紙と鉛筆を⼿放すと,花⼦さんが紙・鉛筆の順で⼿にとって紙に「こんにちは」と書くことになり,めでたく2⼈とも⽬的を達することが出来る。
数分後
順序づけによるデッドロック回避n ある銀⾏⼝座から別の銀⾏⼝座に処理を⾏うプログラムにおけるデッドロックの原因
a1 から a2 へ送⾦する同期コード a1 のロックを取得 ⇨ a2 のロックを所得
a2 から a1 へ送⾦する同期コード a2 のロックを取得 ⇨ a1 のロックを所得
この順番でロックを取得しているのが、デッドロックの原因
両⽅の同期コードが、a1 のロックを取得 ⇨ a2 のロックを所得
という順番でロックを取得しようとすれば、デッドロックは起こらない︕
デッドロック
太郎 花子
送⾦トランザクションクラスの改良n 送⾦プログラムの例では,ロック⽤オブ
ジェクトは, Account のオブジェクトを使⽤していた。幸い、Account型オブジェクトには⼝座番号という⼀意の番号が割り振られている。つまり,⼝座番号の⼩さい⽅からロックを取得するようにすればよい。
デッドロックを起こす送⾦トランザクションクラス: TransferTransaction.java
⼝座クラス: Account.java
⼝座番号
デッドロックを起こす送⾦トランザクションクラス: TransferTransaction.java(改良版)
• ⼝座番号が⼩さい⽅の⼝座オブジェクトを firstLocked に設定し、
• ⼝座番号が⼤きい⽅の⼝座オブジェクトを secondLocked に設定
まず firstLocked からロックを取得し,つぎに secondLocked からロックを取得するようになっている。
デッドロックが発⽣せず,正常にプログラムが動作することがわかる。
3.待機と通知によるスレッド協調
待機と通知によるスレッド強調⾏いたい処理によっては,スレッドを協調させて動作させる必要が出てくる。
例えば,スレッド A がリソースを⽣産し,スレッド B がそのリソースを消費するような場合である。このような関係を,⼀般に「プロデューサー(⽣産者)/コンシューマー(消費者)」関係と⾔う。
例えば、以下のような例が考えられる。• 通信スレッド(⽣産者)がデータを受信し,そのデータを描画スレッド(消費者)が受け取って描写する。• 在庫管理スレッド(⽣産者)が⼊荷処理を⾏い,販売出荷スレッド(消費者)が在庫品を発送する。• 協⼒型ゲームソフトで,参加者の待合室スレッド(⽣産者)が協⼒プレイ希望プレイヤーの受付及び登録
処理を⾏い, マッチングスレッド(消費者)がプレイヤーどうしを組み合わせて協⼒プレイに送り出す。
このようなケースで重要なのは,• ⽣産者スレッド が リソースを⽣産するまで,消費者スレッド を「待機」させ,• ⽣産が終わったら,待機していた 消費者スレッド にその旨を「通知」して再開させるという仕組みである
java では, Object クラスに⽤意されている wait( ), notify( ), notifyAll( ) というメソッドで,スレッドの「待機」と「通知」の仕組みを提供している
スレッドの同期コードが ロック⽤オブジェクトの wait() メソッドを呼ぶと,ロックを解放して,そのスレッドは 待機状態 に⼊る。
待機
wait() によって 待機状態 に⼊ったスレッドを再開させるには,ロック⽤オブジェクトの notify() メソッドか notifyAll() メソッドによって「通知」を⾏う。
再開通知
• notify() メソッド ⇨ どれかひとつだけ に「通知」( スレッドを指定できない!! )• notifyAll() メソッド ⇨ すべてのスレッド に「通知」
• nofity() またはnotifyAll() を呼び出したスレッド(の同期コード)⾃体⇨ ロックを解放⇨ 待機状態
n クラスObjectのスレッド関連メソッド
例: ケーキ屋のシミュレーション
前回のケーキ屋のシミュレーションを変更したもので,ケーキ屋の店頭でケーキが売り切れた場合は,ケーキを焼いて追加するという処理を加える。客は,「待機」と「通知」の仕組みを使って,以下の⾏動を取るようにする。
ケーキが売り切れた場合は,ケーキが追加されるまで待つ ケーキが追加されたらケーキを買う
ケーキ屋シミュレーションクラス: ShopSim.java
新たに導⼊した addANewCake( ) を呼んで,5個のケーキを追加している
以下のフィールを定義• id ... 最後に発⾏した識別番号• myid ...客の識別番号• shop ... 対象となるケーキ屋
コンストラクタで myid と shop フィールドを設定
run( ) メソッドでは,shop. sellCake( ) を呼び出してケーキ屋にケーキを売ってもらった結果を表⽰
10個のケーキの在庫がある状態でケーキ屋オブジェクトを⽣成
客スレッドを15個⽣成・実⾏
待機と通知を組み込んだケーキ屋クラス: CakeShop.java
あらたに加えられた同期メソッドaddANewCake( ) の定義で,ケーキを加えた後, notifyAll( ) を呼んで「通知」を⾏っている。
ケーキが1個以上になるまで「待機」を⾏っている。
ケーキが焼けましたよ︕
notifyAll()
それに気づく客は⼀⼈のみであることに注意。
以下のフィールドの定義• numOfCake ... 店頭に置いてあるケーキの数
numOfCakeを初期化するコンストラク
在庫数を返すアクセッサ
ケーキを1個売る処理
待機が解除されたら,まだ待機しつづけるべきか条件判定をし,待機しなくてもいいときだけ,ループを抜けて,処理を⾏う ようにする必要がある。
理由1. 同じロック⽤オブジェクトの wait( ) で待機状態に⼊っているスレッドでも,待機の理由
⾃体はスレッドごとに異なっている可能性があるためである。つまり,「通知」されて再開したものの,実はそのスレッドの待機条件はまだ真のままであることがあるわけだ。• 例. ケーキ屋の前で待っている客は,新しいケーキが追加されるのを待っているとは
かぎらず,クッキーが焼き上がるのを待っているのかもしれない。
2. 「通知」を受け取った後から,待機条件が真になってしまうこともあり得る。• 例. ケーキが追加されてスレッドに「通知」された後で,待機していたスレッド が
ケーキを買ってしまい,ふたたび0個になったところで,別の待機していたスレッドが再開されるかもしれない。
⼀般に,「待機」は以下のようなループの形で⾏うべきである。
top related