[c#] List.Add () 스레드 안전성

일반적으로 목록이 스레드로부터 안전하지 않다는 것을 이해합니다. 그러나 스레드가 목록에서 다른 작업 (예 : 순회)을 수행하지 않는 경우 목록에 항목을 추가하는 데 문제가 있습니까?

예:

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



답변

배후에서는 버퍼 재 할당 및 요소 복사를 포함하여 많은 일이 발생합니다. 이 코드는 위험을 초래합니다. 아주 간단히 말해서 목록에 추가 할 때 원자 적 연산이 없으며 최소한 “Length”속성이 업데이트되어야하고 항목이 올바른 위치에 있어야하며 (별도의 변수가있는 경우) 인덱스가 필요합니다. 업데이트 할. 여러 스레드가 서로를 짓밟을 수 있습니다. 그리고 성장이 필요하다면 더 많은 일이 진행됩니다. 목록에 무언가를 쓰고 있다면 다른 어떤 것도 그것을 읽거나 쓰지 않아야합니다.

.NET 4.0에는 쉽게 스레드로부터 안전하고 잠금이 필요하지 않은 동시 컬렉션이 있습니다.


답변

현재 접근 방식은 스레드로부터 안전하지 않습니다. 기본적으로 데이터 변환을 수행하기 때문에 PLINQ가 더 나은 접근 방식이 될 수 있기 때문에이 모든 것을 피하는 것이 좋습니다 (이는 간단한 예이지만 결국에는 각 트랜잭션을 다른 “상태로 프로젝션합니다.” “개체).

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


답변

물어 보는 것은 불합리한 일이 아닙니다. 이 있습니다 그들이라는 유일한 방법이 있다면 다른 방법과 결합하여 스레드 안전 문제를 일으킬 수있는 방법이 안전 경우가.

그러나 리플렉터에 표시된 코드를 고려할 때 이것은 분명히 해당되지 않습니다.

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

비록 EnsureCapacity그 자체가 스레드 세이프 (그리고 가장 확실하지 않음)이더라도, 위의 코드는 증분 연산자에 대한 동시 호출이 잘못된 쓰기를 유발할 가능성을 고려할 때 분명히 스레드 세이프가 아닐 것입니다.

잠금, ConcurrentList 사용 또는 잠금없는 큐를 여러 스레드가 쓰는 장소로 사용하고, 작업을 완료 한 후 직접 또는 목록을 채워서 읽습니다 (나는 여러 동시 쓰기와 단일 스레드 읽기가 여기에서 귀하의 패턴입니다. 그렇지 않으면 Add호출 된 유일한 방법이 어디 에서 어떤 용도로 사용될 수 있는지 알 수 없습니다 .)


답변

List.add여러 스레드에서 사용 하고 순서에 신경 쓰지 않는 경우 List어쨌든 인덱싱 기능이 필요하지 않으며 대신 사용 가능한 동시 컬렉션 중 일부를 사용해야합니다.

이 충고를 무시하고 만하면 스레드를 안전하게 add만들 수 add있지만 다음과 같이 예측할 수없는 순서로 만들 수 있습니다 .

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

...

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

이 예측할 수없는 순서를 수락하면 앞서 언급 한대로에서와 같이 인덱싱을 수행 할 수있는 컬렉션이 필요하지 않을 가능성이 있습니다 someList[i].


답변

List가 배열 위에 만들어지고 스레드로부터 안전하지 않기 때문에 문제가 발생할 수 있습니다. 스레드의 위치에 따라 범위를 벗어난 예외 또는 일부 값이 다른 값을 재정의 할 수 있습니다. 기본적으로하지 마십시오.

잠재적 인 문제가 여러 개 있습니다 … 그냥하지 마세요. 스레드로부터 안전한 컬렉션이 필요한 경우 잠금 또는 System.Collections.Concurrent 컬렉션 중 하나를 사용합니다.


답변

ConcurrentBag<T>대신 사용하여 내 문제를 해결 List<T>했습니다.

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


답변

스레드가 목록에서 다른 작업을 수행하지 않는 경우 단순히 항목을 목록에 추가하는 데 문제가 있습니까?

짧은 대답 : 예.

긴 대답 : 아래 프로그램을 실행하십시오.

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();
    }
}