“线程安全”对象是什么意思?

7

我在C#集合中使用了通用队列,但大家都说最好使用System.Collection.Generic.Queue对象,因为它是线程安全的。

请就使用Queue对象做出正确决策,并说明它如何保证线程安全。

3个回答

31
“线程安全”是一个有点不幸的术语,因为它没有一个明确的定义。基本上,它意味着在通过多个线程操作对象时,该对象的某些操作保证会表现得合理。
考虑最简单的例子:一个计数器。假设你有两个线程在递增一个计数器。如果事件顺序如下:
- 线程一从计数器读取,得到零。 - 线程二从计数器读取,得到零。 - 线程一将零递增,将一写入计数器。 - 线程二将零递增,将一写入计数器。
那么请注意计数器“丢失”了一个递增。简单的计数器递增操作并不是线程安全的。为了使它们线程安全,你可以使用锁或 InterlockedIncrement。
同样的道理也适用于队列。非线程安全的队列可能会像非线程安全的计数器一样“丢失”入队操作。更糟糕的是,如果在多线程场景中不正确地使用它们,非线程安全的队列甚至可能会崩溃或产生不合理的结果。
“线程安全”的难点在于它没有一个明确定义。它只是意味着“不会崩溃”吗?这是否意味着会产生合理的结果?例如,假设你有一个“线程安全”的集合。下面的代码是否正确?
if (!collection.IsEmpty) Console.WriteLine(collection[0]);
不行。即使集合是“线程安全的”,这也不意味着这段代码是正确的;另一个线程可能在检查后但在写入行之前将集合清空,因此即使对象据称是“线程安全的”,此代码也可能崩溃。实际上,确定每个相关操作的线程安全性是一个极其困难的问题。
现在来谈谈你实际的情况:任何告诉你“你应该使用 Queue 类,因为它是线程安全的”的人可能并没有清楚地了解他们所说的内容。首先,Queue 不是线程安全的。其次,如果您只在一个线程上使用对象,则无论 Queue 是否线程安全都完全无关紧要!如果您有一个将在多个线程上访问的集合,则如我在上面的示例中所示,您需要解决一个非常困难的问题,而这与集合本身是否“线程安全”无关。您必须确定您对集合执行的每个操作的线程安全性。这是一个非常困难的问题,如果您遇到这样的问题,则应该使用此困难主题的专家的服务。

5
+1 对于可靠的专家证言表示赞同。您指出“线程安全”是一个相当模糊的概念也是正确的。 - Andrew Hare
1
我喜欢同一个人回答所有与线程安全相关的问题,我已经阅读了大约2个小时了。 - Gil Sand
这就是硬件锁指令的作用。在整个RMW操作期间,只有一个核心可以拥有缓存行。请求锁定L3缓存片的第一个核心被批准,这会导致它向其他核心发送无效命令,并且在被核心解锁之前,其他核心无法进行RFO(我认为它会拒绝请求。从手册中模糊记忆)。 - Lewis Kelsey
还有,为什么其他答案说并发呢?这是并行。 - Lewis Kelsey

5

线程安全的类型可以在多个线程中被安全地访问,而不用考虑并发问题。通常这意味着该类型是只读的。

有趣的是,Queue<T> 不是线程安全的 - 只要队列没有被修改,它就可以支持并发读取,但这与线程安全不同。

为了思考线程安全,请考虑如果两个线程正在访问一个 Queue<T> ,而第三个线程在添加或删除该队列时会发生什么。由于该类型不限制此行为,因此它不是线程安全的。


1
在处理多线程编程时,通常需要处理并发问题。"并发问题"是指由于两种不同执行上下文之间的指令交错可能性而特别引入的问题,这两个上下文共享一个资源。在这里,就线程安全而言,执行上下文是进程中的两个线程;但是,在相关主题中,它们可以是进程。
线程安全措施主要有两个目标。首先是恢复确定性,以防止某些任务被留下一半或两个上下文依次写入同一内存位置,这取决于线程上下文的切换(否则由操作系统控制,因此在用户级程序中基本上是不确定性的)。大多数措施仅使用少量受硬件支持的test-and-set指令和类似的软件级synchronization constructs,以强制所有其他执行上下文远离正在进行不能中断工作的数据类型。
通常,只读对象是线程安全的。许多非只读对象可以在多个线程中进行数据访问(只读),而不会出现问题,如果对象在中间没有被修改。但这并不是线程安全。线程安全是指对数据类型进行各种操作,以防止一个线程对其进行修改导致数据损坏或死锁,即使处理许多并发读写也是如此。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接