5

私がこのようなものを持っている場合(擬似コード):

class A
{
    List<SomeClass> list;

    private void clearList()
    {
        list = new List<SomeClass>();
    }

    private void addElement()
    {
        list.Add(new SomeClass(...));
    }
}

両方の関数を並行して実行すると、マルチスレッドの問題(または予期しない動作)が発生する可能性はありますか?

ユースケースはエラーのリストであり、いつでもクリアできます(新しい空のリストを割り当てるだけです)。

編集:私の仮定は

  • 1つのスレッドだけが要素を追加します
  • クリア操作が問題なく成功する限り、忘れられた要素は問題ありません(つまり、クリアしてから新しい要素を追加するまでの競合状態)。
  • .NET 2.0
4

6 に答える 6

10

ここで問題が発生する可能性は2つあります。

  • 新しく追加したアイテムは、クリアして新しいリストを作成するため、すぐに忘れてしまう可能性があります。それは問題ですか?基本的に、AddElementClearListが同時に呼び出された場合、競合状態が発生します。要素は、新しいリストに含まれるか、古い(忘れられた)リストに含まれることになります。
  • List<T>マルチスレッドミューテーションには安全ではないため、2つの異なるスレッドAddElementが同時に呼び出した場合、結果は保証されません。

あなたが共有リソースにアクセスしていることを考えると、私はそれにアクセスしている間、個人的にロックを保持します。ただし、アイテムを追加する直前/直後にリストをクリアする可能性を考慮する必要があります。

編集:2つの理由から、1つのスレッドからのみ追加する場合は問題ないという私のコメントは、すでにいくぶん疑わしいものでした。

  • List<T>まだ完全に構築されていないに追加しようとする可能性があります(私は思います!) 。よくわかりません。.NET2.0メモリモデル(ECMA仕様のモデルとは対照的に)はそれを回避するのに十分強力かもしれませんが、言うのは難しいです。
  • 追加スレッドがlist変数への変更をすぐに「認識」せず、古いリストに追加する可能性があります。確かに、同期がなけれ、古い値を永遠に見ることができます

「GUIでの反復」をミックスに追加すると、非常に注意が必要になります。反復中にリストを変更できないためです。これに対する最も簡単な解決策は、おそらくリストのコピーを返すメソッドを提供することであり、UIはそれを安全に繰り返すことができます。

class A
{
    private List<SomeClass> list;
    private readonly object listLock = new object();

    private void ClearList()
    {
        lock (listLock)
        {
            list = new List<SomeClass>();
        }
    }

    private void AddElement()
    {
        lock (listLock)
        {
            list.Add(new SomeClass(...));
        }
    }

    private List<SomeClass> CopyList()
    {
        lock (listLock)
        {
            return new List<SomeClass>(list);
        }
    }

}
于 2010-02-13T13:03:46.460 に答える
2

はい-可能です、。実際、これらが本当に同時に呼び出されている場合は、可能性が高いです。

さらに、addElementへの2つの別々の呼び出しが同時に発生した場合にも、問題が発生する可能性があります。

この種のマルチスレッドの場合、リスト自体の周りにある種の相互に排他的なロックが本当に必要なので、基になるリストで一度に呼び出すことができる操作は1つだけです。

これを回避する大まかなロック戦略が役立ちます。何かのようなもの:

class A
{
    static object myLock = new object()
    List<SomeClass> list;

    private void clearList()
    {
        lock(myLock)
        {
          list = new List<SomeClass>();
        }

    }

    private void addElement()
    {
        lock(myLock)
        {
          list.Add(new SomeClass(...));
        }
    }
}
于 2010-02-13T13:04:19.910 に答える
2

.NET(3.5まで)のコレクションは、スレッドセーフまたは非ブロッキング(並列実行)ではありません。IListから派生して実装し、すべてのアクションを実行するためにReaderWriterLockSlimを使用する必要があります。たとえば、Addメソッドは次のようになります。

    public void Add(T item)
    {
        _readerWriterLockSlim.EnterWriteLock();
        try { _actualList.Add(item); }
        finally { _readerWriterLockSlim.ExitWriteLock(); }
    }

ここでは、いくつかの並行性のトリックに注意する必要があります。たとえば、新しいインスタンスをIListとして返すGetEnumeratorが必要です。実際のリストではありません。そうしないと、問題が発生します。これは次のようになります。

    public IEnumerator<T> GetEnumerator()
    {
        List<T> localList;

        _lock.EnterReadLock();
        try { localList= new List<T>(_actualList); }
        finally { _lock.ExitReadLock(); }

        foreach (T item in localList) yield return item;
    }

と:

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>)this).GetEnumerator();
    }

注:スレッドセーフまたは並列コレクション(および実際には他のすべてのクラス)を実装する場合は、クラスから派生しないでください。ただし、インターフェースを使用してください。そのクラスの内部構造や仮想ではないいくつかのメソッドに関連する問題が常にあり、それらを非表示にする必要があるためなどです。これを行う必要がある場合は、慎重に行ってください。

于 2010-02-13T15:38:36.767 に答える
1

クリアしたいときに新しいリストを作成するのは適切ではありません。

nullポインター例外に遭遇しないように、コンストラクターでリストも割り当てたと仮定します。

クリアして要素を追加すると、古いリストに追加できますが、問題ないと思いますか?ただし、2つの要素を同時に追加すると、問題が発生する可能性があります。

マルチスレッドタスクを処理するための.Net4の新しいコレクションを調べてください:)

追加: .Net 4を使用している場合は、名前空間System.Collections.Concurrentを調べてくださいSystem.Collections.Concurrent.ConcurrentBag<T>

また、注意しないと、ロックによってパフォーマンスが大幅に低下する可能性があることにも注意してください。

于 2010-02-13T13:05:31.483 に答える
1

このクラスの1つのインスタンスを複数のスレッドで使用する場合は、そうです。あなたは問題にぶつかるでしょう。.Net Framework(バージョン3.5以前)のすべてのコレクションはスレッドセーフではありません。特に、別のスレッドがコレクションをいじくり回しているときにコレクションを変更し始めたとき。

ロックを使用して、マルチスレッド環境で「コピー」コレクションを配布するか、.Net 4.0を使用できる場合は、新しい同時コレクションを使用します。

于 2010-02-13T13:06:30.720 に答える
0

あなたの質問への編集から、あなたがここで通常の犯人を本当に気にしていないことは明らかです-同じオブジェクトのメソッドへの同時呼び出しは実際にはありません。

基本的に、並列スレッドからアクセスしているときにリストに参照を割り当てても大丈夫かどうかを尋ねています。

私が理解している限り、それでも問題が発生する可能性があります。それはすべて、ハードウェアレベルで参照割り当てがどのように実装されているかに依存します。より正確には、この操作がアトミックであるかどうか。

特にマルチプロセッサ環境では、プロセスがアクセス時に部分的にしか更新されなかったため、プロセスが破損した参照を取得する可能性があります。

于 2010-02-13T13:50:13.417 に答える