51

"C# 4 in a Nutshell" で、著者は、このクラスが 0 なしで書き込めることがあることを示していますがMemoryBarrier、私の Core2Duo では再現できません。

public class Foo
{
    int _answer;
    bool _complete;
    public void A()
    {
        _answer = 123;
        //Thread.MemoryBarrier();    // Barrier 1
        _complete = true;
        //Thread.MemoryBarrier();    // Barrier 2
    }
    public void B()
    {
        //Thread.MemoryBarrier();    // Barrier 3
        if (_complete)
        {
            //Thread.MemoryBarrier();       // Barrier 4
            Console.WriteLine(_answer);
        }
    }
}

private static void ThreadInverteOrdemComandos()
{
    Foo obj = new Foo();

    Task.Factory.StartNew(obj.A);
    Task.Factory.StartNew(obj.B);

    Thread.Sleep(10);
}

この必要性は私にはクレイジーに思えます。これが発生する可能性のあるすべてのケースをどのように認識できますか? プロセッサが操作の順序を変更した場合、動作が変わらないことを保証する必要があると思います。

わざわざバリアを使用しますか?

4

6 に答える 6

74

このバグを再現するのは非常に困難です。実際、.NETFrameworkを使用してそれを再現することは決してできないと私は言います。その理由は、Microsoftの実装が書き込みに強力なメモリモデルを使用しているためです。つまり、書き込みは揮発性であるかのように扱われます。揮発性書き込みにはロック解放セマンティクスがあります。つまり、以前のすべての書き込みは、現在の書き込みの前にコミットする必要があります。

ただし、ECMA仕様のメモリモデルは脆弱です。したがって、理論的には、Monoまたは.NETFrameworkの将来のバージョンでさえバグのある動作を示し始める可能性があります。

つまり、私が言っているのは、バリア#1と#2を削除しても、プログラムの動作に影響が及ぶ可能性は非常に低いということです。もちろん、これは保証ではなく、CLRの現在の実装のみに基づく観察です。

バリア#3と#4を取り除くことは間違いなく影響を及ぼします。これは実際にはかなり簡単に再現できます。この例自体ではありませんが、次のコードはよく知られているデモンストレーションの1つです。リリースビルドを使用してコンパイルし、デバッガーの外部で実行する必要があります。バグは、プログラムが終了しないことです。ループThread.MemoryBarrier内に呼び出しを行うか、としてマークを付けることで、バグを修正できます。whilestopvolatile

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");
        t.Join();
    }
}

一部のスレッドのバグを再現するのが難しい理由は、スレッドのインターリーブをシミュレートするために使用するのと同じ戦術で実際にバグを修正できるためです。Thread.Sleepこれはメモリバリアを生成するため、最も注目すべき例です。whileループ内に電話をかけ、バグがなくなることを確認することで、それを確認できます。

あなたが引用した本からの例の別の分析については、ここで私の答えを見ることができます。

于 2010-08-24T13:25:51.050 に答える
10

2 番目のタスクが実行を開始するまでに最初のタスクが完了している可能性は非常に高いです。両方のスレッドがそのコードを同時に実行し、その間にキャッシュ同期操作がない場合にのみ、この動作を確認できます。コードに 1 つあり、StartNew() メソッドはスレッド プール マネージャー内のどこかでロックを取得します。

2 つのスレッドでこのコードを同時に実行するのは非常に困難です。このコードは数ナノ秒で完了します。オッズを得るには、何十億回も試行し、可変遅延を導入する必要があります。もちろん、これはあまり意味がありませんが、本当の問題は、これが予期しないときにランダムに発生する場合です

これには近づかず、lock ステートメントを使用して、健全なマルチスレッド コードを記述してください。

于 2010-08-24T12:48:33.813 に答える
2

マルチスレッドのバグを再現するのは非常に困難です。通常、テスト コードを何度も (数千回) 実行し、バグが発生した場合にフラグを立てる自動チェックを行う必要があります。いくつかの行の間に短い Thread.Sleep(10) を追加しようとするかもしれませんが、それがない場合と同じ問題が発生することが常に保証されるわけではありません。

メモリ バリアは、マルチスレッド コードのハードコアで低レベルのパフォーマンス最適化を行う必要がある人のために導入されました。ほとんどの場合、他の同期プリミティブ、つまり volatile や lock を使用した方がよいでしょう。

于 2010-08-24T12:31:40.573 に答える
2

と を使用するvolatilelock、メモリ バリアが組み込まれます。ただし、そうでない場合は必要です。そうは言っても、あなたの例が示す半分の数が必要だと思います。

于 2010-08-24T12:28:16.440 に答える
1

2 つの異なるスレッドからのデータにアクセスしている場合、これが発生する可能性があります。これは、プロセッサが速度を向上させるために使用するトリックの 1 つです。これを行わないプロセッサを構築することもできますが、はるかに遅くなるため、現在は誰もそれを行っていません。おそらく、Hennessey や Pattersonなどを読んで、さまざまな種類の競合状態をすべて理解する必要があります。

私は常にモニターやロックなどの高レベルのツールを使用していますが、内部的には同様のことを行っているか、バリアを使用して実装されています。

于 2010-08-24T12:34:41.190 に答える