【问题标题】:assign same value to static variable in different thread, still need lock?为不同线程中的静态变量分配相同的值,还需要锁吗?
【发布时间】:2015-12-23 23:53:28
【问题描述】:
public class Service
{
   private List<SomeType> _list = null;
   public static List<SomeType> GetSomeTypeList()
   {
      if (_list != null) return _list;
      List<SomeType> _temp = null;
      _temp = FunctionToGetSomeTypeValue();
      _list = _temp;
      return _list;
   }
}

是服务端代码,GetSomeTypeList可以被多个请求(线程)调用,FunctionToGetSomeTypeValue的返回结果即使在不同的线程中也是一样的,是一个静态资源,但是FunctionToGetSomeTypeValue可能会失败,返回null . 我的意图是在需要时获取资源,并在每次调用时尝试获取它,除非它成功。 问题是更新_list、_list = _temp的时候,需要用lock(lockobj) {_list=_temp}加锁吗?

如果两个线程都在调用GetSomeTypeList(),并且都去调用FunctionToGetSomeTypeValue(),一个是成功的,一个是失败的,成功的一个先调用_list=_temp,失败的一个然后分配_list=null。我对这种情况很好,因为最终_list 将处于良好状态,所有调用都只返回_list。

我不明白的是,如果我不使用锁,是否有可能为 _list 对象分配不正确的值(意外值)?不为空,不正确的_temp 地址正确。

所以实际的问题是,假设有变量:static int Obj = 0;

  • 一个线程正在设置Obj = 1;
  • 另一个线程正在设置Obj = 2;

如果Obj没有锁定设置,Obj的值应该是1或2,不会是别的,对吗? 如果Obj 是struct 怎么办?

引用对象(地址)的分配是原子操作吗? 简单类型object(int, struct)呢?

【问题讨论】:

  • 设置引用是原子的。
  • 使用lock有问题吗?

标签: c# thread-safety static-methods


【解决方案1】:

除了其他答案之外,还有一个框架类Lazy&lt;T&gt;,它允许以线程安全的方式对对象进行这种惰性实例化。无需自己编写:

public class Service
{
   private static Lazy<List<SomeType>> _list = new Lazy<List<SomeType>>(
       ()=>FunctionToGetSomeTypeValue(),
       isThreadSafe: true
   );

   public static List<SomeType> GetSomeTypeList()
   {
      return _list.Value;
   }
}

【讨论】:

  • 对,不需要重新发明轮子。线程同步的规则 1:不要这样做。使用更高级的工具为你做这件事:)
【解决方案2】:

分配引用是原子的,所以_list 将始终是一个有效的引用。 GetSomeTypeList() 将始终返回对 List&lt;SomeType&gt; 或 null 的引用。

但是,有可能 2 个线程同时调用它,都通过 if (_list != null) 条件并接收到不同的 List&lt;SomeType&gt; 实例。

我宁愿把lock放在那里

public static List<SomeType> GetSomeTypeList()
{
    if (_list == null)
    {
        lock (_lock)
        {
            if (_list == null)
            {
                _list = FunctionToGetSomeTypeValue();
            }
        }
    }
    return _list;
}

或者,如果您不喜欢 lock,Interlocked.CompareExchange 仅在 _list 仍为空时为其分配新值

public static List<SomeType> GetSomeTypeList()
{
    if (_list == null)
    {
        var temp = FunctionToGetSomeTypeValue();
        Interlocked.CompareExchange(ref _list, temp, null);
    }
    return _list;
}

请注意,在这两种情况下,第一次检查 if (_list == null) 是在没有任何线程同步的情况下完成的。一旦将值分配给_list,就可以安全地检索它而无需同步。

【讨论】:

    【解决方案3】:

    正如 SLaks 已经指出的那样,设置引用是一种原子操作,即在您提到的情况下,不能有无效值。哪个作业会“获胜”只是一个问题,但它始终是一个完整的作业。

    同样如此,例如用于 int,但通常不适用于结构。

    C#语言规范:

    5.5 变量引用的原子性

    以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float、和引用类型。此外,上一个列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读写,包括long、ulong、double和decimal,以及用户定义的类型,不保证是原子的。

    【讨论】:

      【解决方案4】:

      您不需要lock,但至少需要在该方法结束时进行一次空检查。

      可能有两个线程同时调用您的FunctionToGetSomeValueType。如果第一个成功,_list 将被设置为结果。当第二个稍后失败时,它可能会将其重置为null。

      private static object syncLock = new object ();
      public static List<SomeType> GetSomeTypeList()
      {
          if (_list != null) return _list;
          List<SomeType> _temp = FunctionToGetSomeTypeValue();
          if  (_temp != null) // check this! 
              _list = _temp;
          return _list;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        • 2017-09-16
        • 2015-12-11
        • 2017-12-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多