在。net 4.0中看到新的System.Collections.Concurrent命名空间,我非常激动,非常棒!我已经看到ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, ConcurrentBag和BlockingCollection。
有一样东西似乎神秘地丢失了,那就是ConcurrentList<T>。我必须自己写吗(或者从网上得到:))?
我是不是遗漏了什么明显的东西?
在。net 4.0中看到新的System.Collections.Concurrent命名空间,我非常激动,非常棒!我已经看到ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, ConcurrentBag和BlockingCollection。
有一样东西似乎神秘地丢失了,那就是ConcurrentList<T>。我必须自己写吗(或者从网上得到:))?
我是不是遗漏了什么明显的东西?
当前回答
System.Collections.Generic。List<t>对于多个读取器来说已经是线程安全的。试图使它对多个写入器是线程安全的是没有意义的。(原因Henk和Stephen已经提到了)
其他回答
我试过一段时间(也:在GitHub)。我的实现出现了一些问题,我在这里就不赘述了。让我告诉你,更重要的是,我学到了什么。
首先,你不可能得到一个完整的IList<T>的无锁和线程安全的实现。特别是,随机插入和删除是行不通的,除非你也忘记了O(1)随机访问(也就是说,除非你“欺骗”,只是使用某种链表,让索引糟糕透顶)。
我认为可能值得的是一个线程安全的IList<T>的有限子集:特别是一个允许添加并通过索引提供随机只读访问的子集(但没有Insert、RemoveAt等,也没有随机写访问)。
This was the goal of my ConcurrentList<T> implementation. But when I tested its performance in multithreaded scenarios, I found that simply synchronizing adds to a List<T> was faster. Basically, adding to a List<T> is lightning fast already; the complexity of the computational steps involved is miniscule (increment an index and assign to an element in an array; that's really it). You would need a ton of concurrent writes to see any sort of lock contention on this; and even then, the average performance of each write would still beat out the more expensive albeit lockless implementation in ConcurrentList<T>.
在相对罕见的情况下,列表的内部数组需要调整自身的大小,您确实需要付出一点代价。因此,最终我得出结论,这是一个适合的场景,其中仅添加ConcurrentList<T>集合类型是有意义的:当您希望保证在每次调用中添加元素的开销较低时(因此,与平摊性能目标相反)。
它并不是一个像您想象的那样有用的类。
System.Collections.Generic。List<t>对于多个读取器来说已经是线程安全的。试图使它对多个写入器是线程安全的是没有意义的。(原因Henk和Stephen已经提到了)
在读的数量远远超过写的情况下,或者(无论多么频繁)写是非并发的情况下,在写时复制方法可能是合适的。
实现如下所示
lockless blazingly fast for concurrent reads, even while concurrent modifications are ongoing - no matter how long they take because "snapshots" are immutable, lockless atomicity is possible, i.e. var snap = _list; snap[snap.Count - 1]; will never (well, except for an empty list of course) throw, and you also get thread-safe enumeration with snapshot semantics for free.. how I LOVE immutability! implemented generically, applicable to any data structure and any type of modification dead simple, i.e. easy to test, debug, verify by reading the code usable in .Net 3.5
为了让写时复制工作,你必须让你的数据结构有效地保持不可变,也就是说,在你让它们对其他线程可用之后,任何人都不允许改变它们。当你想修改的时候,你
克隆结构 对克隆进行修改 原子地交换对修改后的克隆的引用
Code
static class CopyOnWriteSwapper
{
public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
where T : class
{
while (true)
{
var objBefore = Volatile.Read(ref obj);
var newObj = cloner(objBefore);
op(newObj);
if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
return;
}
}
}
使用
CopyOnWriteSwapper.Swap(ref _myList,
orig => new List<string>(orig),
clone => clone.Add("asdf"));
如果你需要更多的性能,它将有助于非泛化方法,例如,为你想要的每一种类型的修改(添加,删除,…)创建一个方法,并硬编码函数指针克隆和op。
注意事项#1:确保没有人修改(假定)不可变的数据结构是你的责任。在泛型实现中我们无法阻止这种情况,但当专门化到List<T>时,可以使用List. asreadonly()来防止修改。
注意事项2 .注意列表中的值。上面的copy on write方法只保护它们的列表成员关系,但如果你放的不是字符串,而是一些其他可变对象,你必须注意线程安全(例如锁定)。但这与这个解决方案是正交的,例如,可变值的锁定可以很容易地使用而没有问题。你只需要意识到这一点。
注意事项#3如果您的数据结构很大,并且您经常修改它,那么从内存消耗和复制所涉及的CPU成本两方面考虑,全部在写时复制的方法可能是不可取的。在这种情况下,你可能想要使用MS的不可变集合。
ConcurrentList(作为一个可调整大小的数组,而不是一个链表)不容易用非阻塞操作编写。它的API不能很好地转换为“并发”版本。
如果不需要处理太多项,无锁复制和写入方法非常有效。 下面是我写的一个类:
public class CopyAndWriteList<T>
{
public static List<T> Clear(List<T> list)
{
var a = new List<T>(list);
a.Clear();
return a;
}
public static List<T> Add(List<T> list, T item)
{
var a = new List<T>(list);
a.Add(item);
return a;
}
public static List<T> RemoveAt(List<T> list, int index)
{
var a = new List<T>(list);
a.RemoveAt(index);
return a;
}
public static List<T> Remove(List<T> list, T item)
{
var a = new List<T>(list);
a.Remove(item);
return a;
}
}
使用示例: orders_BUY = CopyAndWriteList.Clear(orders_BUY);