スレッド セーフなコレクションを使用する状況
.NET Framework Version 4 では、マルチスレッドの追加操作と削除操作をサポートするために特別に設計された 5 つの新しいコレクション型が導入されています。 スレッド セーフを確保するために、これらの新しい型では、さまざまな種類の効率的なロック同期機構とロック制御の不要な同期機構が使用されます。 同期を行うと操作にオーバーヘッドが追加されます。 オーバーヘッドの量は、使用する同期の種類、実行する操作の種類、およびその他の要因 (コレクションに同時にアクセスしようとするスレッドの数など) によって異なります。
場合によっては、同期のオーバーヘッドがほとんどなく、外部ロックで保護されるスレッド セーフでない同等の型よりもマルチスレッド型の方が、パフォーマンスとスケーラビリティが大幅に向上することがあります。 また、オーバーヘッドが原因で、スレッド セーフな型のパフォーマンスとスケーラビリティが、外部からロックされるスレッド セーフでないバージョンの型と同等かそれ以下になることもあります。
以降では、スレッド セーフなコレクションと、読み取り操作および書き込み操作でユーザー指定のロックを使用するスレッド セーフでない同等のコレクションの使い分けに関する一般的なガイダンスを示します。 パフォーマンスはさまざまな要因に左右されるので、このガイダンスは特定の状況には沿っておらず、すべての状況で有効であるとは限りません。 パフォーマンスが非常に重要な場合、使用するコレクション型を判断する最適な方法は、典型的なコンピューター構成および負荷に基づいてパフォーマンスを計測することです。 このドキュメントでは、次の用語が使用されています。
純粋なプロデューサー/コンシューマー シナリオ
任意のスレッドで要素の追加と削除のどちらか一方のみが実行されます。混合プロデューサー/コンシューマー シナリオ
任意のスレッドで要素の追加と削除の両方が実行されます。高速化
同じシナリオで別の型と比較してアルゴリズムのパフォーマンスが向上すること。スケーラビリティ
コンピューターのコア数に比例したパフォーマンスの向上。 スケーリングするアルゴリズムのパフォーマンスは、コア数が 2 の場合よりも 8 の場合の方が向上します。
ConcurrentQueue(T) とQueue(T)
純粋なプロデューサー/コンシューマー シナリオでは、各要素の処理時間が非常に短い (命令が少ない) 場合、System.Collections.Concurrent.ConcurrentQueue<T> のパフォーマンスは外部ロックを使用する System.Collections.Generic.Queue<T> よりも若干優れる程度です。 このシナリオでは、ConcurrentQueue<T> は、キューへの配置とキューからの取り出しをそれぞれ専用のスレッドが実行している場合に最大のパフォーマンスを発揮します。 この規則を適用しない場合、複数のコアを備えたコンピューターでは、Queue<T> の方が ConcurrentQueue<T> よりもパフォーマンスが若干向上する可能性があります。
処理時間が 500 FLOPS (浮動小数点演算) 以上の場合、2 つのスレッドを使用する規則は ConcurrentQueue<T> に適用されません。この型で非常に優れたスケーラビリティが実現します。 このシナリオでは、Queue<T> はスケーラビリティの点で劣ります。
混合プロデューサー/コンシューマー シナリオでは、処理時間が非常に短い場合、外部ロックを使用する Queue<T> のスケーラビリティは ConcurrentQueue<T> よりも優れています。 ただし、処理時間が 500 FLOPS 以上の場合は、ConcurrentQueue<T> の方がスケーラビリティに優れます。
ConcurrentStack とStack
純粋なプロデューサー/コンシューマー シナリオでは、処理時間が非常に短い場合、プッシュとポップをそれぞれ専用のスレッドが実行しているときは、System.Collections.Concurrent.ConcurrentStack<T> および外部ロックを使用する System.Collections.Generic.Stack<T> のパフォーマンスはほぼ同じであると考えられます。 ただし、スレッド数が増えると、競合が増えることで両方の型のパフォーマンスが低下し、Stack<T> の方が ConcurrentStack<T> よりも優れたパフォーマンスを示すことがあります。 処理時間が 500 FLOPS 以上の場合は、両方の型がほぼ同じスケーラビリティを示します。
混合プロデューサー/コンシューマー シナリオでは、作業負荷の大小にかかわらず、ConcurrentStack<T> の方が高速です。
PushRange および TryPopRange を使用すると、アクセス時間が大幅に高速化される場合があります。
ConcurrentDictionary とDictionary
一般に、複数のスレッドから同時にキーまたは値を追加および更新するシナリオでは、System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> を使用します。 更新を頻繁に行い、読み取りは比較的少ないシナリオでは、通常、ConcurrentDictionary<TKey, TValue> には大きな利点はありません。 読み取りも更新も多いシナリオでは、通常、コンピューターに任意の数のコアを備えられる場合は ConcurrentDictionary<TKey, TValue> の方が大幅に高速です。
更新を頻繁に行うシナリオでは、ConcurrentDictionary<TKey, TValue> で同時実行の程度を上げて、コンピューターのコア数が多いほどパフォーマンスが向上するかどうかを計測できます。 同時実行レベルを変更する場合、グローバル操作はできるだけ避けてください。
キーまたは値の読み取りのみを行う場合、ディクショナリがスレッドによって変更されないときは同期が不要なので、Dictionary<TKey, TValue> の方が高速です。
ConcurrentBag
純粋なプロデューサー/コンシューマー シナリオでは、System.Collections.Concurrent.ConcurrentBag<T> は他の同時実行コレクション型よりもパフォーマンスの点で劣ると考えられます。
混合プロデューサー/コンシューマー シナリオでは、通常、作業負荷の大小にかかわらず、ConcurrentBag<T> は他の同時実行コレクション型よりもはるかに高速でスケーラビリティに優れます。
BlockingCollection
境界ブロッキング セマンティクスが必要な場合は、System.Collections.Concurrent.BlockingCollection<T> の方がカスタム実装よりもパフォーマンスの点で優れると考えられます。 この型では、高度なキャンセル、列挙、および例外処理もサポートされます。