关于线程安全的大多数建议涉及以下模式的一些变化:
public class Thing
{
private readonly object padlock = new object();
private IDictionary stuff, andNonsense;
public Thing()
{
this.stuff = new Dictionary();
this.andNonsense = new Dictionary();
}
public IDictionary Stuff
{
get
{
lock (this.padlock)
{
if (this.stuff.Count == 0)
this.stuff = this.SomeExpensiveOperation();
return this.stuff;
}
}
}
public IDictionary AndNonsense
{
get
{
lock (this.padlock)
{
if (this.andNonsense.Count == 0)
this.andNonsense = this.AnotherExpensiveOperation();
return this.andNonsense;
}
}
}
// Rest of class...
}
如果get操作昂贵且无关,则单个锁定对象不适合,因为对Stuff
的调用会阻止对AndNonsense
的所有调用,从而降低性能。而不是为每个调用创建一个锁定对象,获取成员本身的锁定是不是更好(假设它不是为了那个目的而实现SyncRoot
或某些东西?例如:
public IDictionary Stuff
{
get
{
lock (this.stuff)
{
if (this.stuff.Count == 0)
this.stuff = this.SomeExpensiveOperation();
return this.stuff;
}
}
}
奇怪的是,我从未见过这种推荐或警告过的方法。我错过了一些明显的东西吗?
2010年5月24日编辑
我做了一些大的改动,因为我真的把我的榜样搞得一团糟。为了清晰起见,我试图简化示例,这是正确的。
编辑摘要:
- 第一个示例中的锁定对象不再是静态的(我的原始示例来自静态类)。
- 字段/属性不再是字符串,并在构造函数中初始化,因此永远不会为null。
- 在
lock{...}
块内移动了return语句。
答案 0 :(得分:4)
您的“标准建议”版本非常糟糕 - 它会对整个AppDomain使用单个锁定。通常,锁对象是实例变量,而不是静态变量。
现在,至于你的建议:更糟糕的是,IMO - 因为你不知道还有什么会锁定它。其他一些代码可能使用相同的字符串 - 同样锁定它。现在假设两个线程获取两个锁而不是一个。当你不知道锁定什么时,除了运气不好之外,你没有希望避免死锁。 (还有codeka提到的无效问题。)
此外,如果变量的值发生变化,则会开始变得混乱 - 然后您可能会同时在锁定块内执行两个线程,这可能是您试图避免的。
只要有可能,您应该(IMO)锁定只有您的代码知道的内容。虽然这本身不会让你自由或安全死锁,但它是一个很好的起点,因为它可以更容易推理你的代码。没有什么可说的,每个实例必须有一个锁 - 你可以根据需要创建更细粒度的锁 - 但如果可能的话,让它们保密。
答案 1 :(得分:3)
好吧,我猜你实际上没有尝试过这个,因为lock(null)
会抛出一个ArgumentNullException
,所以你实际上不能这样做。
您可以使用单独的“stuffLock”对象,如下所示:
class Thing
{
private string stuff;
private object stuffLock = new Object();
public string Stuff
{
get
{
lock(stuffLock)
{
if (stuff == null)
stuff = "Still threadsafe";
}
return stuff;
}
}
}
答案 2 :(得分:3)
在这种情况下,我会使用ReaderWriterLockSlim
,当您直接或通过升级锁输入“写入”锁时,只允许您完全锁定多个重叠的“读取”锁定。或者,您可以使用Thread.MemoryBarrier
无锁,但这很棘手,并且作为一种高级技术需要一些专门的测试以确保它确实有效。
答案 3 :(得分:1)
请注意,请注意,您的示例不本质上是线程安全的,因为您正在查询要阻止外部锁定的变量的值。
另一个线程可以获取切片,就在第一个运行Stuff {get;}的线程释放锁之后,并在第一个线程实际返回this.stuff
之前再次将this.stuff的值设置为null。答案 4 :(得分:0)
同样@codeka points out的问题,请注意lock()
需要引用类型,因此您建议的系统必须为值类型提出不同的方法
答案 5 :(得分:0)
永远不要锁定类型或字符串,或者可能共享的其他对象应用多个appdomains。锁定一个实例字段是没有问题的,只需确保您的实例具有对它的独占访问权。