C# 优雅地取消线程 – 为什么这么长
本文关键字:为什么 线程 取消 | 更新日期: 2023-09-27 18:32:10
最近我正在研究Joe Albahari的多线程星系的奇妙指南。我发现了一件我无法解释的事情:设置取消变量后,执行最后一个循环需要花费大量时间(就其他部分的执行时间而言)。我尝试了易失性方法,还有储物柜对象和 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秒)。
这被称为线程竞赛,是线程代码中的经典错误。 您的程序还遇到了一个称为争用的问题。
争用 首先,您可以轻松地从程序产生输出的速率中看出 Write() 方法很难获得写锁。 这是因为您有 3 个线程在读取,并且它们每个线程在迭代循环时只在很短的时间内释放读锁,最多是纳秒。 这还不足以让 Write() 方法在获取写锁方面有一个不错的机会,它需要 Windows 线程调度程序的帮助才能获得它。 这为阻塞时间过长(秒)的线程提供了优先级提升,以给他们一个机会。
这种争用几乎确保了写入线程在 EnterWriteLock() 调用中都停滞不前。 这是在_cancel检查之后。 他们最终将获取锁并再执行一次写入。
尝试使用 CancellationToken
类
您可以实现它来代替_cancel
变量。
此外,请考虑使用 .网络的线程安全集合类。在这种情况下,不要使用 List<T>
而是使用 ConcurrentBag<T>
类。