简体   繁体   English

List.Add() 线程安全

[英]List.Add() thread safety

I understand that in general a List is not thread safe, however is there anything wrong with simply adding items into a list if the threads never perform any other operations on the list (such as traversing it)?我知道通常 List 不是线程安全的,但是如果线程从未对列表执行任何其他操作(例如遍历它),那么简单地将项目添加到列表中有什么问题吗?

Example:例子:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});

Behind the scenes lots of things happen, including reallocating buffers and copying elements.在幕后发生了很多事情,包括重新分配缓冲区和复制元素。 That code will cause danger.该代码将导致危险。 Very simply, there are no atomic operations when adding to a list, at the least the "Length" property needs to be updates, and item needs to be put in at the right location, and (if there's a separate variable) the index needs to be updated.很简单,添加到列表时没有原子操作,至少“长度”属性需要更新,项目需要放在正确的位置,并且(如果有单独的变量)索引需要要被更新。 Multiple threads can trample over each other.多个线程可以相互践踏。 And if a grow is required then there is lots more going on.如果需要增长,那么还有更多的事情要做。 If something is writing to a list nothing else should be reading or writing to it.如果某些东西正在写入列表,则不应再读取或写入它。

In .NET 4.0 we have concurrent collections, which are handily threadsafe and don't require locks.在 .NET 4.0 中,我们有并发集合,它们是线程安全的并且不需要锁。

You current approach is not thread-safe - I would suggest avoiding this altogether - since you basically do a data transformation PLINQ might be a better approach ( I know this is a simplified example but in the end you are projecting each transaction into another "state" object).您当前的方法不是线程安全的-我建议完全避免这种情况-因为您基本上进行数据转换PLINQ 可能是更好的方法(我知道这是一个简化的示例,但最终您将每个事务投影到另一个“状态” “ 目的)。

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();

If you want to use List.add from multiple threads and do not care about the ordering, then you probably do not need the indexing ability of a List anyway, and should use some of the available concurrent collections instead.如果您想从多个线程使用List.add并且不关心排序,那么您可能无论如何都不需要List的索引功能,而应该使用一些可用的并发集合来代替。

If you ignore this advice and only do add , you could make add thread safe but in unpredictable order like this:如果您忽略此建议而只执行add ,则可以使add线程安全,但顺序不可预测,如下所示:

private Object someListLock = new Object(); // only once

...

lock (someListLock)
{
    someList.Add(item);
}

If you accept this unpredictable ordering, chances are that you as mentioned earlier do not need a collection that can do indexing as in someList[i] .如果您接受这种不可预测的排序,那么您可能如前所述不需要像someList[i]那样可以进行索引的集合。

I solved my problem using ConcurrentBag<T> instead of List<T> like this:我使用ConcurrentBag<T>而不是List<T>解决了我的问题,如下所示:

ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});

It's not an unreasonable thing to ask.问这个问题并不是没有道理的。 There are cases where methods which can cause thread-safety issues in combination with other methods are safe if they are the only method called.某些情况下,如果它们是唯一被调用的方法,那么与其他方法结合会导致线程安全问题的方法是安全的。

However, this clearly isn't a case of it, when you consider the code shown in reflector:但是,当您考虑反射器中显示的代码时,这显然不是这种情况:

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
        this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

Even if EnsureCapacity was in itself threadsafe (and it most certainly is not), the above code is clearly not going to be threadsafe, considering the possibility of simultaneous calls to the increment operator causing mis-writes.即使EnsureCapacity本身是线程安全的(而且它肯定不是),考虑到同时调用增量运算符导致错误写入的可能性,上面的代码显然不是线程安全的。

Either lock, use ConcurrentList, or perhaps use a lock-free queue as the place multiple threads write to, and the read from it - either directly or by filling a list with it - after they have done their work (I'm assuming that multiple simultaneous writes followed by single-threaded reading is your pattern here, judging from your question, as otherwise I can't see how the condition where Add is the only method called could be of any use).要么锁定,使用 ConcurrentList,或者可能使用无锁队列作为多个线程写入的地方,并从中读取 - 直接或通过用它填充列表 - 在他们完成他们的工作之后(我假设从您的问题来看,多个同时写入然后单线程读取是您的模式,否则我看不出Add是唯一调用的方法的条件有什么用处)。

This would cause problems, as the List is built over an array and is not thread safe you might get index out of bounds exception or some values overriding other values, depending on where the threads are.这会导致问题,因为 List 是在数组上构建的,并且不是线程安全的,您可能会得到索引越界异常或某些值覆盖其他值,具体取决于线程所在的位置。 Basically, don't do it.基本上,不要这样做。

There are multiple potential problem... Just don't.有多个潜在的问题......只是不要。 If you need a thread safe collection, either use a lock or one of the System.Collections.Concurrent collections.如果需要线程安全集合,请使用锁或 System.Collections.Concurrent 集合之一。

Is there anything wrong with simply adding items into a list if the threads never perform any other operations on the list?如果线程从未在列表上执行任何其他操作,那么简单地将项目添加到列表中有什么问题吗?

Short answer: yes.简短的回答:是的。

Long answer: run the program below.长答案:运行下面的程序。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
    readonly List<int> l = new List<int>();
    const int amount = 1000;
    int toFinish = amount;
    readonly AutoResetEvent are = new AutoResetEvent(false);

    static void Main()
    {
        new Program().Run();
    }

    void Run()
    {
        for (int i = 0; i < amount; i++)
            new Thread(AddTol).Start(i);

        are.WaitOne();

        if (l.Count != amount ||
            l.Distinct().Count() != amount ||
            l.Min() < 0 ||
            l.Max() >= amount)
            throw new Exception("omg corrupted data");

        Console.WriteLine("All good");
        Console.ReadKey();
    }

    void AddTol(object o)
    {
        // uncomment to fix
        // lock (l) 
        l.Add((int)o);

        int i = Interlocked.Decrement(ref toFinish);

        if (i == 0)
            are.Set();
    }
}

As others already said, you can use concurrent collections from the System.Collections.Concurrent namespace.正如其他人所说,您可以使用System.Collections.Concurrent命名空间中的并发集合。 If you can use one of those, this is preferred.如果您可以使用其中之一,这是首选。

But if you really want a list which is just synchronized, you could look at the SynchronizedCollection<T> -Class in System.Collections.Generic .但是如果你真的想要一个只是同步的列表,你可以查看System.Collections.Generic中的SynchronizedCollection<T> -Class。

Note that you had to include the System.ServiceModel assembly, which is also the reason why I don't like it so much.请注意,您必须包含 System.ServiceModel 程序集,这也是我不太喜欢它的原因。 But sometimes I use it.但有时我会使用它。

Even adding elements on different threads is not thread safe.即使在不同线程上添加元素也不是线程安全的。

In C# 4.0 there are concurrent collections (see http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html ).在 C# 4.0 中有并发集合(参见http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM