23

Linq拡張メソッドはアトミックなのだろうか?または、何らかの反復の前に、スレッド間で使用されるオブジェクトを使用lockする必要がありますか?IEnumerable

変数を宣言することvolatileはこれに影響を及ぼしますか?

要約すると、スレッドセーフな操作として最適なのは次のうちどれですか?

1-ロックなし:

IEnumerable<T> _objs = //...
var foo = _objs.FirstOrDefault(t => // some condition

2-ロックステートメントを含む:

IEnumerable<T> _objs = //...
lock(_objs)
{
    var foo = _objs.FirstOrDefault(t => // some condition
}

3-変数を揮発性として宣言する:

volatile IEnumerable<T> _objs = //...
var foo = _objs.FirstOrDefault(t => // some condition
4

3 に答える 3

24

インターフェイスIEnumerable<T>はスレッドセーフではありません。http://msdn.microsoft.com/en-us/library/s793z9y2.aspxのドキュメントを参照してください。

コレクションが変更されない限り、列挙子は有効なままです。要素の追加、変更、削除など、コレクションに変更が加えられた場合、列挙子は回復不能に無効になり、その動作は定義されません。

列挙子には、コレクションへの排他的アクセス権はありません。したがって、コレクションを介して列挙することは、本質的にスレッドセーフな手順ではありません。列挙中のスレッドセーフを保証するために、列挙全体を通してコレクションをロックできます。読み取りと書き込みのために複数のスレッドがコレクションにアクセスできるようにするには、独自の同期を実装する必要があります。

Linqはこれを変更しません。

ロックは明らかに、オブジェクトへのアクセスを同期するために使用できます。ただし、オブジェクトを反復処理するときだけでなく、アクセスするすべての場所でオブジェクトをロックする必要があります。

コレクションを揮発性として宣言しても、プラスの効果はありません。コレクションへの参照の読み取り前と書き込み後にのみ、メモリバリアが発生します。コレクションの読み取りまたは書き込みは同期されません。

于 2012-06-19T15:06:37.447 に答える
10

要するに、それらは上記のようにスレッドセーフではありません。

ただし、これは「あらゆる種類の反復」の前にロックする必要があるという意味ではありません。

コレクションを変更するすべての操作(要素の追加、変更、または削除)を、他の操作(要素の追加、変更、削除、または要素の読み取り)と同期させる必要があります。

コレクションに対して読み取り操作のみを同時に実行している場合は、ロックは必要ありません。(したがって、Average、Contains、ElementAtOrDefaultなどのLINQコマンドをすべて一緒に実行すると問題ありません)

コレクション内の要素がマシンワード長である場合(ほとんどの32ビットコンピューターのIntなど)、その要素の値の変更はすでにアトミックに実行されています。この場合、ロックせずにコレクションに要素を追加したり、コレクションから要素を削除したりしないでください。ただし、デザインで非決定論を処理できる場合は、値を変更しても問題ない場合があります。

最後に、コレクション全体をロックするのではなく、コレクションの個々の要素またはセクションをきめ細かくロックすることを検討できます。

于 2012-06-20T20:45:26.593 に答える
-1

IEnumerableこれは、拡張メソッドがスレッドセーフではないことを証明する例です。私のマシンでは、throw new Exception("BOOM");ラインは常に数秒以内にヒットします。

うまくいけば、スレッドの問題をトリガーする方法を説明するのに十分なコードを文書化しています。

このコードをlinqpadで実行して、自分の目で確かめることができます。

async Task Main()
{
    // The theory is that it will take a longer time to query a lot of items
    // so there should be a better chance that we'll trigger the problem. 
    var listSize = 999999;
    
    // Specifies how many tasks to spin up. This doesn't necessarily mean
    // that it'll spin up the same number of threads, as we're using the thread
    // pool to manage that stuff. 
    var taskCount = 9999;

    // We need a list of things to query, but the example here is a bit contrived. 
    // I'm only calling it `ages` to have a somewhat meaningful variable name. 
    // This is a distinct list of ints, so, ideally, a filter like:
    // `ages.Where(p => p == 4` should only return one result. 
    // As we'll see below, that's not always the case. 
    var ages = Enumerable
        .Range(0, listSize)
        .ToList();
    
    // We'll use `rand` to find a random age in the list. 
    var rand = new Random();
    
    // We need a reference object to prove that `.Where(...)` below isn't thread safe. 
    // Each thread is going to modify this shared `person` property in parallel. 
    var person = new Person();
    
    // Start a bunch of tasks that we'll wait on later. This will run as parallel
    // as your machine will allow. 
    var tasks = Enumerable
        .Range(0, taskCount)
        .Select(p => Task.Run(() =>
        {
            // Pick a random age from the list. 
            var age = ages[rand.Next(0, listSize)];
            
            // These next two lines are where the problem exists. 
            // We've got multiple threads changing `person.Age` and querying on `person.Age` 
            // at the same time. As one thread is looping through the `ages` collection
            // looking for the `person.Age` value that we're setting here, some other
            // thread is going to modify `person.Age`. And every so often, that will
            // cause the `.Where(...)` clause to find multiple values. 
            person.Age = age;
            var count = ages.Where(a => a == person.Age).Count();

            // Throw an exception if the `.Where(...)` filter returned more than one age. 
            if (count > 1) {
                throw new Exception("BOOM");
            }
        }));
        
    await Task.WhenAll(tasks);
    
    Console.WriteLine("Done");
}

class Person {
    public int Age { get; set; }
}
于 2021-06-25T13:53:36.467 に答える