锁定请求的对象是个坏主意吗?

时间:2010-05-24 06:49:46

标签: .net thread-safety

关于线程安全的大多数建议涉及以下模式的一些变化:

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语句。
  •   

6 个答案:

答案 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。锁定一个实例字段是没有问题的,只需确保您的实例具有对它的独占访问权。