我试图理解在并发编程中使用不可变数据结构如何能够避免锁定的需要。我在网上看过一些内容,但还没有看到任何具体的例子。
例如,假设我们有一些使用Dictionary< string, object>
周围的锁的代码(C#)执行此操作:
class Cache
{
private readonly Dictionary<string, object> _cache = new Dictionary<string, object>();
private readonly object _lock = new object();
object Get(string key, Func<object> expensiveFn)
{
if (!_cache.ContainsKey("key"))
{
lock (_lock)
{
if (!_cache.ContainsKey("key"))
_cache["key"] = expensiveFn();
}
}
return _cache["key"];
}
}
如果_cache
不可变,那怎么看?是否可以删除lock
并确保expensiveFn
不会被多次调用?
答案 0 :(得分:9)
简短的回答是,它不会,至少不完全。
Immutability仅保证在您使用数据结构时,另一个线程无法修改数据结构的内容。一旦有了实例,该实例永远不会被修改,因此您将始终安全地阅读它。任何编辑都需要制作实例的副本,但这些副本不会直接干扰已经引用的任何实例。
在多线程应用程序中,即使使用不可变对象,仍然有很多理由需要锁定和同步构造。它们主要处理与时间相关的问题,例如竞争条件,或控制线程流,以便在正确的时间进行活动。不可变对象实际上无法帮助解决这些问题。
不变性使多线程更容易,但它不会使轻松。
至于你关于什么是不可变字典的问题。我不得不说,在大多数情况下,在你的例子中,甚至使用不可变字典也没有多大意义。因为它被用作“活动”对象,随着项目的添加和删除而固有地变化。即使是围绕不变性设计的语言,如F#,也有可变对象用于此目的。有关详细信息,请参阅this link。可以找到不可变版本here。
答案 1 :(得分:3)
不可变数据结构减少背后的基本思想(注意我说&#34;减少,&#34;不&#34;消除&#34;)锁定并发的需要是每个线程都在工作本地副本或不可变数据结构,因此不需要锁定(没有线程可以修改任何其他线程&#39;数据,只是他们自己的)。只有当多个线程可以同时修改相同的可变状态时才需要锁定,因为否则您可能会出现脏读和#34;和其他类似的问题。
答案 2 :(得分:2)
为什么不可变数据很重要的一个例子: 假设您有一个由两个不同线程访问的person对象。 如果thread1将人员保存到地图中(人物哈希包含人名),则另一个线程2会更改人名。 现在,在实际存在的情况下,thread1将无法在地图中找到此人!
如果person是不可变的,那么不同线程持有的引用将是不同的,即使user2更改了他的名字,thread1也能够在地图中找到该人(因为将创建一个新的person实例)。