更新对不可变对象的引用的首选方法是什么?

本文关键字:方法 是什么 引用 不可变 对象 更新 | 更新日期: 2023-09-27 18:13:13

如果我们有一个不可变的对象,如immutableelist()。在多线程环境中使用这个对象的首选方法是什么?

public class MutableListOfObjects()
{
   private volatile ImmutableList objList;
   public class MutableListOfObjects()
   {
       objList = new ImmutableList();
   }
   void Add(object o)
   {
      // Adding a new object to a list will create a new list to ensure immutability of lists.
      // Is declaring the object as volatile enough or do we want to
      // use other threading concepts?
      objList = objList.Add(o);
   }
   // Will objList always use that lest version of the list
   bool Exist(object o)
   {
      return objList.Exist(o);
   }
}

声明引用volatile是否足以实现期望的行为?还是使用其他线程函数更好?

更新对不可变对象的引用的首选方法是什么?

"Preferred"是上下文。最简单的方法是使用lock,在大多数情况下,这将非常有效地完成工作。如果你有充分的理由认为lock是一个问题,那么Interlocked是有用的:

bool retry;
do {
    var snapshot = objList;
    var combined = snapshot.Add(o);
    retry = Interlocked.CompareExchange(ref objList, combined, snapshot)
              != snapshot;
} while(retry);

这基本上适用于一个乐观的但检查路径:大多数情况下,它只会通过一次。偶尔有人会在我们不注意的时候改变objList的值——没关系,我们只是再试一次。

然而,有一些线程安全列表等的预封装实现,由真正知道他们在谈论什么的人实现。考虑使用ConcurrentBag<T>等。或者只是一个List<T>和一个lock

一个简单而有效的方法是使用ImmutableInterlocked.Update。你传递给它一个方法来执行添加。它调用你的add方法,然后如果列表在添加过程中没有改变,它会自动将新值分配给objList。如果列表改变了,Update会再次调用你的add方法来重试。它会不断重试,直到能够写入更改。

ImmutableInterlocked.Update(ref objList, l => l.Add(o));

如果你有很多写争用,这样你就会花费太多的时间在重试上,那么在一些稳定的对象(不是objList)上使用锁是更好的。

volatile在这种情况下不会帮助您-它不会在读取objList,调用Add()和分配objList之间创建锁。您应该使用锁定机制。volatile只是防止操作重新分配。

在您的情况下,每次添加对象时都要创建一个新列表-通常更好的替代方法是在本地线程变量中创建列表(这样它就不会受到多线程问题的影响),并且一旦创建列表,将其标记为不可变或为其创建不可变包装器。这样,您将获得更好的性能和内存使用。