web-dev-qa-db-ja.com

C#配列はスレッドセーフですか?

特に

  1. パラメータとして配列とインデックスを取得する関数を作成します。
  2. N要素の配列を作成します。
  3. Nカウントループを作成します。
  4. 新しいスレッドのループ内で、渡されたインデクサーを使用して、オブジェクトの新しいインスタンスを配列に割り当てます。

私はスレッドなどの管理方法を知っています。これがスレッドセーフな方法であるかどうかに興味があります。

 class Program
{
    // bogus object
    class SomeObject
    {
        private int value1;
        private int value2;

        public SomeObject(int value1, int value2)
        {
            this.value1 = value1;
            this.value2 = value2;
        }
    }

    static void Main(string[] args)
    {

        var s = new SomeObject[10];
        var threads = Environment.ProcessorCount - 1;
        var stp = new SmartThreadPool(1000, threads, threads);
        for (var i = 0; i < 10; i++)
        {
            stp.QueueWorkItem(CreateElement, s, i);
        }

    }

    static void CreateElement(SomeObject[] s, int index)
    {
        s[index] = new SomeObject(index, 2);
    }
}
51
Gary

各スレッドが配列の別々の部分でのみ動作する場合、すべてがうまくいくと思います。 share data(つまり、スレッド間で通信する)を使用する場合は、メモリモデルの問題を回避するために、何らかのメモリバリアが必要になります。

I believe一連のスレッドを生成し、それぞれが配列の独自のセクションを生成し、すべてのスレッドがThread.Joinの使用を完了するまで待機する場合、それで十分ですあなたが安全であるための障壁の面で。現時点では、それをサポートするドキュメントはありません。

編集:サンプルコードは安全です。 2つのスレッドが同じ要素にアクセスすることはありません-それぞれが別々の変数を持っているかのようです。ただし、それ自体は有用ではありません。ある時点で、通常、スレッドは状態を共有する必要があります。あるスレッドが別のスレッドが書き込んだ内容を読み取りたい場合があります。それ以外の場合、独自のプライベート変数ではなく共有配列に書き込むことは意味がありません。 That's注意する必要があるポイント-スレッド間の調整。

44
Jon Skeet

配列に関するMSDNドキュメント は言う:

この型のパブリックstatic(Visual BasicではShared)メンバーはスレッドセーフです。インスタンスメンバーは、スレッドセーフであるとは限りません。

この実装は、配列の同期(スレッドセーフ)ラッパーを提供しません。ただし、配列に基づく.NET Frameworkクラスは、SyncRootプロパティを使用して、独自の同期バージョンのコレクションを提供します。

コレクションを列挙することは、本質的にスレッドセーフな手順ではありません。コレクションが同期されている場合でも、他のスレッドがコレクションを変更できるため、列挙子が例外をスローします。列挙中のスレッドの安全性を保証するために、列挙全体でコレクションをロックするか、他のスレッドによる変更に起因する例外をキャッチできます。

だから、彼らはスレッドセーフではありません。

25
Joren

通常、コレクションが「スレッドセーフではない」と言われる場合、同時アクセスが内部的に失敗する可能性があることを意味します(たとえば、別のスレッドがリストの最後に要素を追加しているときにList <T>の最初の要素を読み取ることは安全ではありません:リスト<T>は基になる配列のサイズを変更し、データがコピーされる前に読み取りアクセスが新しい配列に移動する場合があります。

配列は固定サイズであり、そのような「構造の変更」がないため、このようなエラーは配列では不可能です。 3つの要素を持つ配列は、3つの変数ほどスレッドセーフではありません。

C#仕様では、これについて何も言及されていません。しかし、ILを知っていてCLI仕様を読んでいる場合は明らかです-配列内の要素への管理参照(C# "ref"パラメーターに使用されるものなど)を取得し、通常および揮発性の両方のロードとストアを実行できます。 CLI仕様では、そのようなロードとストアのスレッドセーフの保証について説明しています(例:要素の原子性<= 32ビット)

あなたの質問を正しく理解していない場合、異なるスレッドを使用して配列を埋めたいが、各配列要素に一度だけ割り当てますか?もしそうなら、それは完全にスレッドセーフです。

14
Daniel

提供している例は、MicrosoftのC#4.0に対するParallel拡張機能の動作に非常に似ています。

このforループ:

for (int i = 0; i < 100; i++) { 
  a[i] = a[i]*a[i]; 
}

になる

Parallel.For(0, 100, delegate(int i) { 
  a[i] = a[i]*a[i]; 
});

ですから、はい、あなたの例は大丈夫です。 C#の新しい並列サポートに関する古い ブログ投稿 を次に示します。

5
Eric