在阅读Joe Albahari的优秀著作《C#线程》时,我遇到了以下模棱两可的句子:
线程安全类型并不一定使使用它的程序成为线程安全的,后者所涉及的工作通常会使前者变得多余。
(您可以在本页上找到该句子;只需搜索"不确定性"即可快速跳转到相应的部分。)
我希望使用ConcurrentDictionary来实现某些线程安全的数据结构。这段话是不是告诉我ConcurrentDictionary不能保证线程安全地写入我的数据结构?有人能提供一个反例,说明线程安全类型实际上无法提供线程安全吗?
提前感谢您的帮助。
最简单地说,线程安全列表或字典就是一个很好的例子;让每个单独的操作线程安全并不总是足够的-例如,"检查列表是否为空;如果为空,则添加一个项目"-即使所有线程都是安全的,也不能做到:
if(list.Count == 0) list.Add(foo);
因为两者之间可能会发生变化。您需要同步测试和更改。
我对警告的理解是,仅仅因为您使用线程安全变量并不意味着您的程序是线程安全的。
例如,考虑一个具有两个变量的类,这些变量可以从两个线程中进行修改。仅仅因为这些变量是单独的线程安全的,并不能保证类修改的原子性。如果有两个线程修改这些变量,那么一个变量可能最终由一个线程设置值,而另一个则由另一个线程进行设置。这很容易破坏类的内部一致性。
前一段时间我正在进行一些搜索,以解决我在一些线程中遇到的问题,并看到了以下页面:
http://www.albahari.com/threading/part2.aspx#_Thread_Safety
特别是关于"锁定线程安全对象"的部分
从页面:
有时您还需要锁定访问线程安全对象。为了进行说明,假设Framework的List类确实是线程安全的,并且我们想向列表中添加一个项:
if (!_list.Contains (newItem)) _list.Add (newItem);
不管这个列表是否线程安全,这个声明肯定不是!
我认为他的意思是,仅仅在任何地方使用ConcurrentDictionary
而不是Dictionary
并不能使程序线程安全。所以,如果你有一个非线程安全的程序,搜索和替换是没有帮助的;同样,在任何地方添加SynchronizedAttribute
都不会像魔法仙尘一样起作用。对于集合尤其如此,因为迭代总是一个问题[1]。
另一方面,如果将非线程安全的程序重组为更线程安全的设计,那么通常不需要线程安全的数据结构。一种流行的方法是根据相互发送"消息"的"参与者"来重新定义程序——除了单个生产者/消费者风格的消息队列之外,每个参与者都可以独立存在,不需要在内部使用线程安全的数据结构。
[1] BCL集合的第一个版本包括一些"线程安全"集合,这些集合在迭代过程中只是而不是线程安全的。Concurrent集合在迭代期间是线程安全的,但与其他线程的修改同时迭代。其他集合库允许"快照",然后可以对其进行迭代,忽略来自其他线程的修改。
这是一个有点模糊的语句,但考虑一下,例如,一个类有两个成员,每个成员都是线程安全的,但这两个成员都必须以原子方式更新。
在处理这种情况时,您可能会使整个操作成为原子操作,从而使线程安全,从而使对单个成员的线程安全访问变得无关紧要。
如果不意味着您的ConcurrentDictionary将以不安全的方式运行。
我的简要解释是这样的。线程安全有多种形式,满足一种形式的代码不会自动满足所有其他形式。
Roy,
我猜你是在"过度阅读"一个过于简洁的句子。。。我认为这句话有两个意思:
- "仅仅使用线程安全的数据结构并不意味着你的程序能够正确处理多线程……就像线程安全数据结构的存在本质上使你的程序成为多线程一样";然后他接着说
- "除非你准备投入所涉及的"硬代码"(通常需要对相当复杂的场景有非常精确的理解),以使整个程序正确处理线程,否则使用线程安全的数据结构基本上是浪费时间
Ergo:多线程是相当困难的,使用合适的开箱即用的数据结构是任何解决方案的重要组成部分,但它肯定不是整个解决方案。。。除非你准备好仔细考虑(即在家里做同步工作),否则你只是在开玩笑,说数据结构会神奇地"修复"你的程序。
我知道这听起来"有点刺耳",但我的看法是,当很多傻瓜发现编程(在这个充满敌意的图标和GUI画家的开明时代)需要深入思考时,他们真的很失望。谁会砸了它?!?!
干杯。基思。
这段话告诉我了吗ConcurrentDictionary不保证线程安全写入我的数据结构?
不,这不是乔·阿尔巴哈里的意思。ConcurrentDictionary
将始终通过来自多个线程的同时写入来保持一致的状态。另一个线程将永远不会看到处于不一致状态的数据结构。
有人能提供一个显示线程安全类型实际失败提供螺纹安全?
但是,在多线程环境中,从线程安全类型进行的一系列读取和写入仍然可能失败。
void ExecutedByMultipleThreads(ConcurrentQueue<object> queue)
{
object value;
if (!queue.IsEmpty)
{
queue.TryDequeue(out value);
Console.WriteLine(value.GetHashCode());
}
}
很明显,ConcurrentQueue
是一种线程安全类型,但如果另一个线程将IsEmpty
和TryDequeue
方法之间的最后一项退出队列,则此程序仍可能因NullReferenceException
而失败。数据结构本身仍然通过保持一致的状态来提供线程安全保障,但由于程序对线程安全的假设通常是不正确的,因此程序不是线程安全的。在这种情况下,程序是不正确的;而不是数据结构。