为实例创建多个syncLock变量

时间:2011-03-24 17:13:44

标签: c# object locking synchronized

我有两个使用延迟加载支持字段的内部属性,并且在多线程应用程序中使用,因此我按照this MSDN article实现了一个双重检查锁定方案

现在,首先假设这是一个合适的模式,所有示例都显示为实例创建单个锁对象。如果我的两个属性彼此独立,那么为每个属性创建一个锁实例会不会更有效?

我觉得可能只有一个是为了避免死锁或竞争条件。一个明显的情况不会浮现在脑海中,但我相信有人可以给我看一个...(我对多线程代码的经验不是很明显)

private List<SomeObject1> _someProperty1;
private List<SomeObject2> _someProperty2;
private readonly _syncLockSomeProperty1 = new Object();
private readonly _syncLockSomeProperty2 = new Object();

internal List<SomeObject1> SomeProperty1
{
  get
  {
    if (_someProperty1== null)
    {
      lock (_syncLockSomeProperty1)
      {
        if (_someProperty1 == null)
        {
          _someProperty1 = new List<SomeObject1>();
        }
      }
    }
    return _someProperty1;
  }

  set
  {
    _someProperty1 = value;
  }
}

internal List<SomeObject2> SomeProperty2
{
  get
  {
    if (_someProperty2 == null)
    {
      lock (_syncLockSomeProperty2)
      {
        if (_someProperty2 == null)
        {
          _someProperty2 = new List<SomeObject2>();
        }
      }
    }
    return _someProperty2;
  }

  set
  {
    _someProperty2 = value;
  }
}

3 个答案:

答案 0 :(得分:3)

如果您的属性真正独立,那么为每个属性使用独立锁都没有坏处。

答案 1 :(得分:1)

如果两个属性(或更具体的初始值设定项)彼此独立,就像您提供的示例代码一样,有两个不同的锁定对象是有意义的。但是,当初始化很少发生时,效果可以忽略不计。

请注意,您也应该保护setter的代码。 lock语句强加了一个所谓的内存屏障,这对于防止竞争条件的多CPU和/或多核系统尤其必不可少。

答案 2 :(得分:0)

是的,如果它们彼此独立,这确实会更有效率,因为访问一个不会阻止访问另一个。如果这种独立性证明是错误的,那么你也可以获得有关僵局风险的资金。

问题是,假设_someProperty1 = new List<SomeObject1>();不是分配给_someProperty1的真实代码(几乎不值得延迟加载,是吗?),那么问题是:代码可以吗填充SomeProperty1曾经通过任何代码路径调用填充SomeProperty2的内容,反之亦然,无论多么奇怪?

即使一方可以呼叫另一方,也不会出现死锁,但如果两方都可以互相呼叫(或1呼叫2,2呼叫3和3呼叫1,依此类推),则死锁可以肯定会发生。

作为一项规则,我首先使用广泛的锁(一个锁用于所有锁定的任务)然后根据需要使锁更窄作为优化。如果您有20个需要锁定的方法,那么判断安全性会更困难(同样,您只需使用锁定对象开始填充内存)。

请注意,您的代码也有两个问题:

首先,你没有锁定你的二传手。可能这很好(你只是希望你的锁可以防止多次调用加载方法,并且实际上并不关心setget之间是否存在重写),可能这是一场灾难。

其次,根据运行它的CPU,在写入时仔细检查可能会出现读/写重新排序问题,因此您应该有一个volatile字段,或者调用内存屏障。见http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx

编辑:

还值得考虑是否真的需要它。

考虑操作本身应该是线程安全的:

  1. 做了很多事情。
  2. 根据这些东西创建一个对象。
  3. 将该对象分配给本地变量。
  4. 1和2只发生在一个线程上,3是原子的。因此,锁定的优点是:

    1. 如果执行上面的步骤1和/或2有自己的线程问题,并且没有通过自己的锁保护它们,那么锁定是100%必要的。

    2. 如果对步骤1和2中获得的值采取行动并且稍后重复步骤1和2进行操作将是灾难性的,则锁定是100%必需的。

    3. 锁定可防止多次浪费1和2。

    4. 因此,如果我们可以排除案例1和案例2作为一个问题(需要进行一些分析,但这通常是可能的),那么我们只能防止案例3中的浪费担心。现在,也许这是一个很大的担忧。但是,如果它很少出现,并且也没有那么多浪费,那么不锁定的收益将超过锁定的收益。

      如果有疑问,锁定可能是更安全的方法,但它可能只是偶尔浪费的操作生活更好。