일반적으로 목록이 스레드로부터 안전하지 않다는 것을 이해합니다. 그러나 스레드가 목록에서 다른 작업 (예 : 순회)을 수행하지 않는 경우 목록에 항목을 추가하는 데 문제가 있습니까?
예:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
일반적으로 목록이 스레드로부터 안전하지 않다는 것을 이해합니다. 그러나 스레드가 목록에서 다른 작업 (예 : 순회)을 수행하지 않는 경우 목록에 항목을 추가하는 데 문제가 있습니까?
예:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
답변:
배후에서는 버퍼 재 할당 및 요소 복사를 포함하여 많은 일이 발생합니다. 이 코드는 위험을 초래합니다. 아주 간단히 말해서 목록에 추가 할 때 원자 적 연산이 없으며 최소한 "Length"속성이 업데이트되어야하고 항목이 올바른 위치에 있어야하며 (별도의 변수가있는 경우) 인덱스가 필요합니다. 업데이트 할. 여러 스레드가 서로를 짓밟을 수 있습니다. 그리고 성장이 필요하다면 더 많은 일이 진행됩니다. 목록에 무언가를 쓰고 있다면 다른 어떤 것도 그것을 읽거나 쓰지 않아야합니다.
.NET 4.0에는 쉽게 스레드로부터 안전하고 잠금이 필요하지 않은 동시 컬렉션이 있습니다.
ConcurrentList유형이 없습니다 . 동시 백, 사전, 스택, 대기열 등이 있지만 목록은 없습니다.
현재 접근 방식은 스레드로부터 안전하지 않습니다. 기본적으로 데이터 변환을 수행하기 때문에 PLINQ가 더 나은 접근 방식이 될 수 있기 때문에이 모든 것을 피하는 것이 좋습니다 (이는 간단한 예이지만 결국에는 각 트랜잭션을 다른 "상태로 프로젝션합니다." "개체).
List<object> list = transactions.AsParallel()
.Select( tran => new object())
.ToList();
Parallel.Foreach인덱스를 받는 오버로드를 사용하는 것입니다.이 경우 각 스레드가 다른 배열 항목을 조작하므로 안전해야합니다.
물어 보는 것은 불합리한 일이 아닙니다. 이 있습니다 그들이라는 유일한 방법이 있다면 다른 방법과 결합하여 스레드 안전 문제를 일으킬 수있는 방법이 안전 경우가.
그러나 리플렉터에 표시된 코드를 고려할 때 이것은 분명히 해당되지 않습니다.
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].
이 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();
}
}
다른 사람들이 이미 말했듯이 System.Collections.Concurrent네임 스페이스 에서 동시 컬렉션을 사용할 수 있습니다 . 그중 하나를 사용할 수 있다면 이것이 선호됩니다.
당신은 정말 그냥 동기화 목록을 원한다면, 당신은 볼 수 있었다 SynchronizedCollection<T>에서 -Class System.Collections.Generic.
System.ServiceModel 어셈블리를 포함해야했는데, 이것이 제가 그다지 좋아하지 않는 이유이기도합니다. 하지만 때때로 나는 그것을 사용합니다.
다른 스레드에 요소를 추가하는 것조차 스레드로부터 안전하지 않습니다.
C # 4.0에는 동시 컬렉션이 있습니다 ( http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html 참조 ).