スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#による並行プログラミング入門 (5) 排他的アクセス(1) Interlocked

 並行処理を行う場合、各スレッドから同時に同じメモリ部分の値を書き換えようとすると、正常な結果を得ることができません。このような状況をアクセス競合(racingレーシング)というのでした。 
(この心配がないオブジェクトをスレッドセーフというのでした。) 
これを回避する方法は色々あり順次紹介していきたいと思いますが、まずは原始的な昔ながらの手法をいくつか紹介したいと思います。 
 
最初はInterlockedクラスの紹介です。(これはSystem.Threading名前空間で定義されたstaticなクラスです。)MSDNのよると次のように説明されています。 
「Interlocked クラスは、複数のスレッドが共有する変数へのアクセスを同期化するメソッドを提供します。この変数が共有メモリにある場合、各プロセスのスレッドがこの機構を使用できます。インタロックされた操作はアトミックです。つまり、操作全体が 1 つの単位のため、同じ変数の別のインタロックされた操作によって中断されることはありません。」 
つまり、インタロッククラスのメソッドは、ひと塊の処理として実行されることが保障されていて、他のスレッドから割り込み不能だということです。 
 
 
整数(int32,int64)の値に対して複数のスレッドから変更を加える場合はAddメソッドを使用すると簡単にできます。 
Interlocked.Add(location,val)で、int参照型の値locationに整数値valを加えた値をlocationに格納します。返り値は 「location に格納された新しい値」です。 
この場合は「int参照型の値locationに整数値valを加えた値をlocationに格納する」という操作が外からの割り込みが不可能となります。 
 
例 
//resは結果格納用 
> let res = ref 0;; 
val res : int ref = {contents = 0;} 
 
> Parallel.For(1,101,(fun i  -> Interlocked.Add (res,i)|>ignore));; 
val it : ParallelLoopResult = 
  System.Threading.Tasks.ParallelLoopResult {IsCompleted = true; 
                                             LowestBreakIteration = null;} 
> res;; 
val it : int ref = {contents = 5050;} 
 
ということでちゃんと1から100までの数の和を得ることができました。 
 
アクセス競合をわざとおこしてみると次のようになります。 
 
> let res = ref 0 
Parallel.For(1,101,(fun i  ->Thread.Sleep(10);res:= !res + i));; 
 
val res : int ref = {contents = 4873;}//5050になっていない! 
 
 
次はInterlocked.IncrementとInterlocked.Decrementです。これはInterlocked.Increment(location)で、int参照型の値locationのに1加えた数をlocationに格納します。返り値は 「location に格納された新しい値」です。Decrementは1減らした数となります。 
この場合は「int参照型の値locationのに1加えた数をlocationに格納する」という操作が外からの割り込みが不可能となります。 
 
 
例 
> let res = ref 0 
Parallel.For(1,101,(fun i  -> if(i % 7 = 0) then Interlocked.Increment(res)|>ignore));; 
 
val res : int ref = {contents = 14;} 
 
これで1から100までの数のうち7で割り切れる数の個数を調べています。 
 
さてInterlockedクラスは共有する整数値に整数値を足していったり、デクリメント、インクリメントをするのは手軽なのですが、その他の型について変化をくわえるには手間がかかります。 
参考までに「ひとつだけ共有するfloat型の値に複数のスレッドから数を加える例」を挙げてみます。 
 
これにはInterlocked.CompareExchangeメソッドを利用しますので、まずはこれの説明からです。 
これは、3つの値のタプルを引数にとります。 
CompareExchange(location1,val,comparand)で、location1は参照型であり、まずcomparandと「location1の指し示す値」を比較します。そして比較した結果が等しいときに、「location1の指し示す値」をvalで置き換えます。戻り値は「location1の指し示す値」の元の値です。 
 
次のような関数を定義します。 
 
> let rec myFloatAdd oriPointer (addend:float) = 
    let initVal = !oriPointer  
    let addedVal = initVal + addend //この作業の間にoriPointerの指し示す値は変化する可能性あり 
    //次のif内のCompareExchangeはまずinitValとoriPointerの指し示す値を比較する。 
    //変化ない場合(割り込みがなし)は、addedValでpriPointerの指し示す値を置き換える 
    //この作業は割り込みの心配なし(Interlockedのメソッドなので) 
    //この場合は返り値とinitValが同じ値となり、ifのconditionがtrueとなり終了 
    if initVal = Interlocked.CompareExchange(oriPointer,addedVal,initVal) then 
        (); 
    //割り込みがあった場合は再チャレンジ 
    else myFloatAdd oriPointer addend 
;; 
 
val myFloatAdd : float ref -> float -> unit 
 
では使用してみます。 
 
> let resF = ref 0.0 
Parallel.For(1,101,(fun i  ->Thread.Sleep(10); myFloatAdd resF (float i)));; 
 
val resF : float ref = {contents = 5050.0;} 
 
なおInterlocked.CompareExchangeメソッドもいくつかのオーバーロードをもちますので、興味ある方はMSDNで調べてみて下さい。 
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。