0

最近,我正在研究Joe Albahari 撰写的多线程星系的精彩指南。我发现一件事我无法解释:设置取消变量后,执行最后一个循环需要很长时间(就其他部分的执行时间而言)。我尝试过 volatile 方法,也尝试过 locker 对象和 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  

我期望的是,在 Canceled Line 之后没有更多内容,或者执行速度更快,例如在 1:52:23.02* 40 *

(上面的结果是当程序直接从命令提示符运行时,当从 Visual Studio 中运行时,“间隙”更小,但仍然接近 0.5 秒)。

4

2 回答 2

1

这被称为线程竞赛,是线程代码中的一个经典错误。您的程序还遇到了一个称为争用的问题。

首先是争用,您可以从程序产生输出的速率很容易看出,Write() 方法很难获得写锁。那是因为您有 3 个线程正在读取,并且它们每个都只在短的时间内释放读取锁,充其量是一纳秒,同时它们迭代循环。这还不足以让 Write() 方法有机会获得写锁,它需要 Windows 线程调度程序的帮助才能获得成功。这为阻塞时间过长(秒)的线程提供了优先级提升,从而为它们提供了机会。

这种争用几乎确保了写入线程都在 EnterWriteLock() 调用上停滞不前。在_cancel检查之后。他们最终将获得锁并再执行一次写入。

于 2013-09-19T13:29:54.787 回答
0

Try using the CancellationToken classes

You can implement it in place of your _cancel variable.

Also, consider using .Net's thread safe collection classes. In this case instead of using List<T> use the ConcurrentBag<T> class.

于 2013-09-19T12:56:39.870 回答