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

时间:2013-05-13 11:20:08

标签: c# multithreading synchronization immutability

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

例如

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是否足以实现所需的行为?或者最好使用其他线程函数?

4 个答案:

答案 0 :(得分:6)

“首选”是语境。 最简单的方法是使用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

答案 1 :(得分:3)

你的补充是非原子的,因此容易受到竞争条件的影响。例如:

void Add(object o)
{
    // This code is not thread safe:
    objList = objList.Add(o); // context switch after add, before assignment == bad
}

所以,你添加到一个新的列表,但在分配发生之前,另一个线程进入...添加其他东西,分配。您之前的线程进行了分配。糟糕。

您需要将作业包装在锁定

void Add(object o)
{
    lock(objList)
    {
        objList = objList.Add(o); // only one thread can do this at any given time
    }
}

答案 2 :(得分:1)

一种简单有效的方法是使用ImmutableInterlocked.Update。您向其传递了执行添加的方法。如果添加期间列表没有更改,它将调用您的add方法,然后自动将新值分配给objList。如果列表更改,则Update再次调用您的add方法以重试。它会一直重试,直到能够写入更改为止。

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

如果您有大量写争用,以致您在重试上花费了太多时间,那么最好对某个稳定的对象(而不是objList)使用锁。

答案 3 :(得分:0)

在这种情况下,

volatile无法帮助您 - 它不会在阅读objList,调用Add()和分配objList之间建立锁定。您应该使用锁定机制。 volatile只是防止操作重新分配。

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