C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

ログ内検索
  • キーワードを複数指定する場合は 半角スペース で区切ってください。
  • 検索条件は、(AND)=[A かつ B] (OR)=[A または B] となっています。
  • [返信]をクリックすると返信ページへ移動します。
キーワード/ 検索条件 /
検索範囲/ 強調表示/ ON (自動リンクOFF)
結果表示件数/ 記事No検索/ ON
大文字と小文字を区別する

No.65894 の関連記事表示

<< 0 >>
■65894  Re[8]: 変数のアトミック性を保障するコーディング方法について
□投稿者/ yoh2 -(2013/03/22(Fri) 02:49:22)
    No65877 (howling さん) に返信
    
    > てっきり最適化で入れ替えが行われているのかと、もろに勘違いしてました。
    > うーん、ここらへんになってくると、そういう物もあるのか…くらいな認識でいた方が良いのでしょうか。
    > 実際に動かしてみて、デバッグする際に「この変数おかしいぞ?」となると思うのですが、
    > その可能性の1つとして、こういった物がある、という程度という意味です。
    
    同期を取るのにロックを必ず使うならば雑学程度の扱いでもよいかと思いますが、
    アトミック操作をフルに使ってロックフリーにしようと思うならどういうときにどう
    おかしくなるかを把握しておかなければハマると思います。
    
    C#で、メモリ操作の順が期待と異なってしまう例を書いてみました。あまり自信ありませんが。
    
    Windows 7 x64, Visual Studio 2012, .Net Framework 4.5, Any CPU,
    実CPU: Opteron 6376 で、UseBarrier = falseにすると
    [何かヘン!] のコードが実行され、UseBarrier = trueにすると実行されない
    (数分間チェックしただけですが)ことを確認できました。
    # C#でvolatile使ったことがないので使い方を間違えてるかも。
    
        public class Program
        {
            // ふたつのスレッドのロックに使うフラグたち。
            private static volatile bool[]  Locks = { false, false };
            // ごく一瞬を除いてほとんどの期間 -1。
            private static volatile int Value = -1;
            // true: メモリバリアを使う。false: メモリバリアを使わない。
            private static bool UseBarrier = false;
    
            public static void Main(string[] args)
            {
                List<Thread> threads = new List<Thread>();
                for (int i = 0; i < 2; i++)
                {
                    // 引数 = 0でThreadBodyを実行するスレッドと
                    // 引数 = 1でThreadBodyを実行するスレッドを
                    // ひとつずつ作る。
                    Thread thread = new Thread(ThreadBody);
                    thread.Start(i);
                    threads.Add(thread);
                }
    
                foreach (Thread thread in threads)
                {
                    // ここで待ってても終わらないので確認が終わったら強制終了して下さい。
                    thread.Join();
                }
            }
    
            private static void ThreadBody(object id)
            {
                Console.WriteLine("Thread #{0} started.", id);
                for (; ; )
                {
                    DoSomething((int)id);
                }
            }
    
            private static void DoSomething(int id)
            {
                Locks[id] = true;
                if (UseBarrier) Thread.MemoryBarrier();
    
                // ここのwhile条件を抜ける瞬間はLocks[自ID] == true
                // かつLocks[他ID] == false。
                // つまり、メモリ読み書きが機械語の順に実行されているなら
                // 他スレッドは同時にはこの判定を行えないので、以下しばらくの
                // 範囲は同時に実行されることがない。
                while (Locks[id ^ 1])
                {
                    Locks[id] = false;
                    if (UseBarrier) Thread.MemoryBarrier();
                    Locks[id] = true;
                    if (UseBarrier) Thread.MemoryBarrier();
                }
                // 同時に1スレッドしか実行されないはずの範囲 -- ここから
    
                int tmp = Value;
                if (tmp != -1)
                {
                    // [何かヘン!]
                    // ここに入る条件を満たすには、他スレッドが(★)の部分にいなければならないが、
                    // メモリ操作の追い越しがなければそんなことにはならないはず。
                    Console.WriteLine("Thread #{0}: Value = {1}, myLock = {2}, anotherLock = {3}",
                        id, tmp, Locks[id], Locks[id ^ 1]);
                }
                Value = id;
                if (UseBarrier) Thread.MemoryBarrier();
                // (★) Valueが-1以外の値を取るのはこの部分だけ。
                Value = -1;
                if (UseBarrier) Thread.MemoryBarrier();
                // 同時に1スレッドしか実行されないはずの範囲 -- ここまで
                Locks[id] = false;
                if (UseBarrier) Thread.MemoryBarrier();
            }
        }
    
    <余談>
    上記コード、以前GCC向けのCで書いたコードを、余計な部分を削りつつC#に移植したものです。
    元コードはこちら。
    http://slashdot.jp/journal/544783
    </余談>
記事No.65737 のレス /過去ログ111より / 関連記事表示
削除チェック/



<< 0 >>

パスワード/

- Child Tree -