3

C#parallelメソッドをテストしようとしていますが、これが私のテスト プログラムです。

class Program
{
    static int counter;
    static void Main(string[] args)
    {
        counter = 0;
        Parallel.Invoke(
            () => func(1),
            () => func(2),
            () => func(3)
            );
        Console.Read();
    }


    static void func(int num)
    {
        for (int i = 0; i < 5;i++ )
        {
            Console.WriteLine(string.Format("This is function #{0} loop. counter - {1}", num, counter));
            counter++;
        }
    }
}

私がやろうとしたことは、1つの静的共有変数を持ち、各関数インスタンスがそれを1ずつ増やすことです.

counter私はそれが順番に印刷されることを期待していましたが(1,2,3,...) 、出力は驚くべきものです:

This is function #1 loop. counter - 0
This is function #1 loop. counter - 1
This is function #1 loop. counter - 2
This is function #1 loop. counter - 3
This is function #1 loop. counter - 4
This is function #3 loop. counter - 5
This is function #2 loop. counter - 1
This is function #3 loop. counter - 6
This is function #3 loop. counter - 8
This is function #3 loop. counter - 9
This is function #3 loop. counter - 10
This is function #2 loop. counter - 7
This is function #2 loop. counter - 12
This is function #2 loop. counter - 13
This is function #2 loop. counter - 14

なぜこれが起こっているのか誰にも説明できますか?

4

3 に答える 3

2

問題は、コードがスレッドセーフでないことです。たとえば、次のようなことが起こります。

  • 関数 #2 は、counterそれを使用するためにの値を取得しますConsole.WriteLine()
  • 関数 #1 は、 の値を取得しcounter、 を呼び出しConsole.WriteLine()、インクリメントしますcounter
  • 関数 #1 は、 の値を取得しcounter、 を呼び出しConsole.WriteLine()、インクリメントしますcounter
  • 関数 #2 は最終的Console.WriteLine()に古い値で呼び出します

また、++それ自体はスレッドセーフではないため、最終的な値が 15 にならない場合があります。

これらの問題の両方を修正するには、次を使用できますInterlocked.Increment()

for (int i = 0; i < 5; i++)
{
    int incrementedCounter = Interlocked.Increment(ref counter);
    Console.WriteLine("This is function #{0} loop. counter - {1}", num, incrementedCounter);
}

このようにして、元のコードのように、インクリメント前ではなく、インクリメント後に数値を取得します。また、このコードでも数字が正しい順序で出力されませんが、各数字が 1 回だけ出力されることは確実です。

数字を正しい順序で並べたい場合は、次を使用する必要がありますlock

private static readonly object lockObject = new object();

…

for (int i = 0; i < 5; i++)
{
    lock (lockObject)
    {
        Console.WriteLine("This is function #{0} loop. counter - {1}", num, counter);
        counter++;
    }
}

もちろん、これを行うと、実際には並列処理が得られませんが、これは実際のコードではないと思います。

于 2013-05-01T13:50:47.907 に答える
1

実際に何が起こるか - Invoke はこれらのタスクをキューに入れるだけで、ランタイムはこれらのタスクにスレッドを割り当てます。これにより、多くのランダムな要素が与えられます (どれが最初に取得されるかなど)。

msdn の記事でさえ、次のように述べています。

このメソッドを使用して、一連の操作を並列で実行できます。操作が実行される順序や、並列で実行されるかどうかについては保証されません。このメソッドは、正常終了または例外終了のどちらによる完了かに関係なく、提供された各操作が完了するまで戻りません。

于 2013-05-01T11:46:25.653 に答える