Threading.Interlocked.Exchangeについて。

タグの編集
投稿者 みどりこぶた  (高校生) 投稿日時 2017/6/10 18:23:48
マルチスレッド、排他制御について。
Threading.Interlocked.Exchangeについて。

幾つかの作業を並行したいと思いマルチスレッドをやってみようと思いました。
調べたところ下記のサイトが分かりやすかったので参考にしました。
http://www.atmarkit.co.jp/ait/articles/0505/25/news113_3.html
そこで、スレッドや変数・オブジェクトのロック取得(?)はコストが高いため条件により使い分けるべきだ、と理解しました。
page3,(3)にThreading.Interlocked.Exchangeとあります。
「変数へ値を設定する」と書いてありますがこの操作はもともとスレッドセーフ(?)なわけではないのでしょうか?

試しに、
    Public Class ClassTest
        Private i As Integer = 0, k As Integer = 1
        Public Sub main()
            Dim th As Threading.Thread
            th = New Threading.Thread(New Threading.ThreadStart(Sub()
                                                                    Do : i = k : Loop
                                                                End Sub)) With {.IsBackground = True}
            th.Start()
            th = New Threading.Thread(New Threading.ThreadStart(Sub()
                                                                    Do : k = i : Loop
                                                                End Sub)) With {.IsBackground = True}
            th.Start()
            MsgBox("ずっと待っていればエラーが...でない…?")
        End Sub
    End Class
という感じでやってみたのですが、一向に例外は吐かれません。
調べたところ、プロセッサのキャッシュやメインメモリとの間で整合性が保証されない(?)とかいうことで、
つまりはその「変数へ値を設定する」という作業を分割できない処理として行う、ことなのかなと理解しました。
そして、上記のプログラムで例外にならないのは、その型が偶然プロセッサが一度に扱えるbit数の範囲内であるから、と理解しました。
従って、環境により異なるため、その偶然はCLI(.net内部?)レベルでは保証されない、と。
しかしそれを保証するThreading.Interlocked.Exchangeなどの関数がある、と。

この認識はあっていますか?
いろいろな型で上記プログラムを試してみましたが、一つも例外にはなりませんでした。skylakeの64bitなのですが古いPCとかだと例外になったりするのですか?



改めて質問をまとめると、複数のスレッドから一つの変数をアクセスする際は、きちんとThreading.Interlocked.Exchangeなどでアクセスするべきなのですか?

vb.net、vs2015で書いてます。勉強とかではなく趣味で、高校生です。常識無いかもしれませんが、指摘いただけると助かります。
前回「初投稿な人」で投稿しましたが、改めました。 
投稿者 YuO  (社会人) 投稿日時 2017/6/11 00:02:56
現実的な話としては,Integer型のフィールドの代入において,Interlockedクラスの各メソッドを使う必要はありません。
Long型は64bitプロセスならばやはり不要です。

で,Interlocked.Exchangeに関しては,件のページの記述だけだと不十分です。

「Interlockedであること」を無視するならば,Interlocked.Exchangeは以下のような動作になります。
Public Shared Function Exchange (ByRef location1 As Integer, value As IntegerAs Integer
    Dim result = location1 '※1 
    location1 = value '※2 
    Return result
End Funciton


このコードだと※1と※2の間に別のコードがlocation1を書き換える可能性があります。
そうすると,上記のExchangeの戻り値は「valueを設定する直前のlocation1の値」ではなくなってしまいます。
そうでないことを防いでくれるのが,Interlocked.Exchangeになります。

これを使うと何ができるかというと,「フラグが立っていなければフラグを立てて作業する」というようなことができます。
# see) MSDN https://msdn.microsoft.com/ja-jp/library/d3fxt78a(v=vs.110).aspx
ただ,InterlockedのExchangeよりはCompareExchangeの方が,この目的には使えてしまったりしますが……。


ちなみに,スレッド間でアクセスの競合が起きても各種例外は発生しません。
さらに,競合が起きたことを認識する方法も基本的にはありません (起きた後にデータ不整合を検知できればわかる)。
なので,競合を起こさないようにプログラムを作る必用があります。
投稿者 みどりこぶた  (高校生) 投稿日時 2017/6/11 12:55:27
回答ありがとうございます、非常に勉強になりますm(_ _)m


>これを使うと何ができるかというと,「フラグが立っていなければフラグを立てて作業する」というようなことができます。
>ちなみに,スレッド間でアクセスの競合が起きても各種例外は発生しません。さらに,競合が起きたことを認識する方法も基本的にはありません。
勘違いを浮き彫りにしてくださりありがとうございます。
「変数へ値を設定する」と同時に前の値を取得することがミソなのですね。
サイトも読み直してみるとそう書いてありましたね^^;
"Exchange"…読んで字の如くですよね…。
また、競合は例外にはならないのですね。私のテストプログラムは根本から間違っていますね。



一応これで解決なのですが…、ただ、気になるのが、、、
>Long型は64bitプロセスならばやはり不要です。
これはbit幅が関係するよということ、もしくはその強調ですか?
単に変数に値をセットするだけの場合でも、型やプラットフォームによってそうとは限らない、という意味なのでしょうか?

例えば、上記の条件に合わない [型T] である [変数V] があるとして、そこに複数のスレッドからそれぞれ [値A]、[値B] を代入し続けた場合、[変数V] は [値A] でも [値B] でもない、壊れたような値である場合があるということですか?
Private V As T
スレッド1、Do: V = A: Loop
スレッド2、Do: V = B: Loop


脱線してるかもしれませんが、よろしくお願いします。

さらにさらに無関係な質問なのですが、YuO様の返答のサンプルコードが枠線で囲まれ文字が色付けされていると思いますが、どうするとそう書けるのでしょうか…?
投稿者 YuO  (社会人) 投稿日時 2017/6/12 02:38:20
> 一応これで解決なのですが…、ただ、気になるのが、、、
> >Long型は64bitプロセスならばやはり不要です。
> これはbit幅が関係するよということ、もしくはその強調ですか?
> 単に変数に値をセットするだけの場合でも、型やプラットフォームによってそうとは限らない、という意味なのでしょうか?

・32bitプロセス:32bit単位でメモリにアクセスする
・64bitプロセス:64bit単位でメモリにアクセスする
という違いがあります。
そして,Long型のフィールドに対しての読み書きは
・32bitプロセス:2回のアクセスが必要
・64bitプロセス:1回のアクセスが必要
となります。

32bitプロセスでLong型のフィールドにアクセスする場合,
他のスレッドが書き込もうとしているとアクセスに競合が発生する可能性があります。
・自スレッドの書き込みと他スレッドの書き込みが競合し,どちらが書き込んだでも無い値が書き込まれる可能性
・自スレッドの読み込みと他スレッドの書き込みが競合し,書き込み前でも書き込み後でも無い,本来ありえない値が読まれる可能性

> 例えば、上記の条件に合わない [型T] である [変数V] があるとして、そこに複数のスレッドからそれぞれ [値A]、[値B] を代入し続けた場合、[変数V] は [値A] でも [値B] でもない、壊れたような値である場合があるということですか?
あり得ます。Long型のフィールドに値を代入する場合に,32bitプロセスでは,
1. スレッド1が上位32bitを書き込む
2. スレッド2が上位32bitを書き込む
3. スレッド2が下位32bitを書き込む
4. スレッド1が下位32bitを書き込む
という順序の書き込みが起こる可能性があります。
ここで,スレッド2が書き込んだ上位32bitとスレッド1が書き込んだ下位32bitからなるLong値ができああがります。

> さらにさらに無関係な質問なのですが、YuO様の返答のサンプルコードが枠線で囲まれ文字が色付けされていると思いますが、どうするとそう書けるのでしょうか…? 
投稿時に表示される,「投稿で使用できる特殊コードの説明」をお詠み下さい。
投稿者 みどりこぶた  (高校生) 投稿日時 2017/6/12 21:53:45
分かりやすく、丁寧な解説ありがとうございます。

一人で悶々としてましたが、スッキリです( ゚д゚)

> 投稿時に表示される,「投稿で使用できる特殊コードの説明」をお詠み下さい。 
すみません、見逃してました…。親切感謝です。


もしまた質問する機会がありましたら、よろしくお願いします。