一般に、リストはスレッドセーフではないことを理解していますが、スレッドがリストに対して他の操作(トラバースなど)を実行しない場合、単にアイテムをリストに追加することに何か問題がありますか?
例:
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を参照)。