如果我们有一个像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是否足以实现所需的行为?或者最好使用其他线程函数?
答案 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
只是防止操作重新分配。
在您的情况下,每次添加对象时都会创建一个新列表 - 通常更好的选择是在本地线程变量中创建列表(以便它不受多线程问题影响)和一次创建列表,将其标记为不可变或为其创建不可变包装器。这样,您将获得更好的性能和内存使用。