0

最近、Joe Albahari によるマルチスレッドの銀河への素晴らしいガイドを研究していました。説明できないことが 1 つあります。キャンセル変数を設定した後、最後のループを実行するのに (他の部分の実行時間に関して) かなりの時間がかかります。私は揮発性アプローチ、ロッカーオブジェクトとMemoryBarier呼び出しも試しましたが、結果は常に同じです

class Program
{
    static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
    static List<int> _items = new List<int>();
    static Random _rand = new Random();
    static /*volatile*/ bool _cancel = false;
    static object _cancelLock = new object();

    static void Main()
    {
        Thread t1 = new Thread(Read); t1.Start();
        Thread t2 = new Thread(Read); t2.Start();
        Thread t3 = new Thread(Read); t3.Start();

        Thread t4 = new Thread(Write); t4.Start("A");
        Thread t5 = new Thread(Write); t5.Start("B");

        System.Console.ReadKey();
        Console.WriteLine("Cancelling... " + DateTime.Now.ToString("hh:mm:ss.ffff"));
        _cancel = true;
        Console.WriteLine("Cancelled " + DateTime.Now.ToString("hh:mm:ss.ffff"));


    }

    static void Read()
    {
        while (true)
        {
            _rw.EnterReadLock();
            foreach (int i in _items) Thread.Sleep(10);
            _rw.ExitReadLock();
        }
    }

    static void Write(object threadID)
    {
        while (true)
        {
            if (_cancel)
              break;

            int newNumber = GetRandNum(100);
            _rw.EnterWriteLock();
            _items.Add(newNumber);
            _rw.ExitWriteLock();
            Console.WriteLine("Thread " + threadID + " added " + newNumber+" at "+DateTime.Now.ToString("hh:mm:ss.ffff"));
            //Thread.Sleep(100);
        }
    }

    static int GetRandNum(int max) { lock (_rand) return _rand.Next(max); }
}

出力を見てください:

Thread B added 37 at 01:52:20.2916  
Thread B added 64 at 01:52:20.2916  
Thread B added 89 at 01:52:20.2926  
Thread B added 92 at 01:52:20.2926  
Thread B added 55 at 01:52:20.2926  
Thread B added 60 at 01:52:20.2926  
Thread B added 0 at 01:52:20.2926  
Thread A added 74 at 01:52:20.2926  
Thread A added 90 at 01:52:20.2926  
Thread A added 86 at 01:52:20.2926  
Thread A added 91 at 01:52:20.2926  
Thread A added 19 at 01:52:20.2926  
Thread A added 67 at 01:52:20.2926  
Thread A added 52 at 01:52:20.2926  
Thread A added 73 at 01:52:20.2926  
Thread A added 39 at 01:52:20.2926  
Thread A added 24 at 01:52:20.2926  
Thread B added 0 at 01:52:20.2926  
cCancelling... 01:52:23.0229  
Cancelled 01:52:23.0229  
Thread A added 93 at 01:52:26.0542  
Thread B added 83 at 01:52:26.0542  

私が期待するのは、キャンセルされた行の後に何もないか、1:52:23.02* 40 *のように、より速く実行される行があることです。

(上記の結果は、プログラムがコマンド プロンプトから直接実行された場合です。Visual Studio 内から実行された場合、「ギャップ」は小さくなりますが、それでもほぼ 0.5 秒です)。

4

2 に答える 2

1

これは、スレッド化されたコードの古典的なバグであるスレッド競合として知られています。あなたのプログラムは、競合と呼ばれる問題にも苦しんでいます。

最初に競合します。プログラムが出力を生成する速度から、Write() メソッドが書き込みロックを取得するのに非常に苦労していることは簡単にわかります。これは、3 つのスレッドの読み取りがあり、それぞれがループを反復している間、読み取りロックを非常に短い時間 (せいぜい 1 ナノ秒) しか解放しないためです。これは、Write() メソッドが書き込みロックを取得するのに適切なショットを与えるには十分ではありません。それを完全に取得するには、Windows スレッド スケジューラの助けが必要です。これにより、ブロックされている時間が長すぎる (秒) スレッドに優先度が上がり、チャンスが与えられます。

この競合により、書き込みスレッドが両方とも EnterWriteLock() 呼び出しで停止することがほぼ確実になります。_cancelチェックの後です。最終的にロックを取得し、もう一度書き込みを実行します。

于 2013-09-19T13:29:54.787 に答える
0

クラスを使ってみるCancellationToken

変数の代わりに実装でき_cancelます。

また、.Net のスレッド セーフ コレクション クラスの使用を検討してください。この場合、使用する代わりにList<T>クラスを使用しますConcurrentBag<T>

于 2013-09-19T12:56:39.870 に答える