【发布时间】:2018-07-27 09:13:13
【问题描述】:
我想知道,如果List<T> 是线程安全的并且读取多个读取器没有问题,但不止一个写入器可能会导致问题。所以我写了下面的测试来看看实际发生了什么。
[TestClass]
public class ListConcurrency
{
[TestMethod]
public void MultipleWritersTest()
{
var taskCnt = 10;
var addCnt = 100;
var list = new List<object>();
var tasks = new List<Task>();
for (int i = 0; i < taskCnt; i++)
{
var iq = i;
tasks.Add(Task.Run(() =>
{
Console.WriteLine("STARTING : " + iq);
for (int j = 0; j < addCnt; j++)
{
try
{
list.Add(new object());
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("FINISHING: " + iq);
}));
}
Task.WhenAll(tasks).Wait();
Console.WriteLine("FINISHED: " + list.Count);
}
}
这是一个示例输出:
STARTING : 0
FINISHING: 0
STARTING : 1
FINISHING: 1
STARTING : 8
STARTING : 9
FINISHING: 9
FINISHING: 8
STARTING : 2
FINISHING: 2
STARTING : 7
STARTING : 3
FINISHING: 3
FINISHING: 7
STARTING : 4
FINISHING: 4
STARTING : 6
FINISHING: 6
STARTING : 5
FINISHING: 5
FINISHED: 979
我对两件事感到惊讶:
- 多次运行测试表明,有时结果列表计数不是预期的 1000 (=10 x 100),而是更少。
- 添加过程中没有异常发生。
如果两者都会发生(预期和错误的项目计数),那将是有意义的......这仅仅是List<T> 展示其非线程安全性的方式吗?
编辑:我的开场白措辞很糟糕,我知道 List<T> 不是线程安全的(例如用于迭代),但我想看看会发生什么,如果它以这种方式被“滥用”。正如我在下面的评论中所写,调试结果(不会抛出异常)可能对其他人有用。
【问题讨论】:
-
List<T>不是线程安全的:list.Add(new object()); -
非线程安全的关键在于它经常导致无法在 100% 的时间内重现的错误。我似乎记得在过去看到它有时会设法取消引用空值,这会引发异常。但是这个测试的point是什么?您现在知道
List<T>不是线程安全的,那么为什么还要努力“证明”它呢? -
您在哪里发现 List 是线程安全的? Documentation(页面底部)声明
It is safe to perform multiple read operations on a List<T>, but issues can occur if the collection is modified while it’s being read.,这就是你正在做的事情。 -
做一些非线程安全的事情就像在十字路口闯红灯一样。有时什么都没有发生。有时会发生巨大的崩溃,每个人都死了。有时在这两个极端之间会发生其他不好的事情。结果永远不能保证是相同的。
-
即使你发现它确实抛出了异常(有时它可能仍然会抛出异常,但你还没有证明它不会),这些信息也毫无意义因为它会抛出异常由于程序员错误。你不应该捕获这些异常,你应该修复你的代码。
标签: c# multithreading concurrency