C#でマルチスレッドプログラミング−3

Parallelクラスを使った並列ループ

.NET Framework4からはSystem.Threading.Tasks 名前空間が提供されていて、より簡単にかけるようになっているようだ。ありがたや、ありがたや。
Parallelクラスを使うとForループを簡単に並列化できる。

using System;
using System.Threading.Tasks;   

class Program
{
    //1スレッドでのループ回数
    private const int NUM_LOOP = 10000000;
    //100以下の自然数の一様乱数をNUM_LOOP個取得してその和を表示
    static void ParallelizedMethod(int id)
    {
        Random x = new Random(id);
        long result = 0;
        for (int i = 0; i < NUM_LOOP; i++)
        {
            result += x.Next(100);
        }
        Console.WriteLine("Thread ID:{0}, Value : {1}", id, result);
    }

    static void Main(string[] args)
    {
        //ParallelizedMethodをそれぞれ3回実行
        Console.WriteLine("correct expression");
        Parallel.For(0, 3, new Action<int>(ParallelizedMethod));
        //省略形の以下でもOK
        Console.WriteLine("shoter expression");
        Parallel.For(0, 3, ParallelizedMethod);
        //Foreach版
        Console.WriteLine("In case of ForEach");
        Parallel.ForEach(new int[3]{0,1,2}, ParallelizedMethod);
    }
}

実行結果

correct expression
Thread ID:0, Value : 494982776
Thread ID:1, Value : 494781791
Thread ID:2, Value : 495154121
shoter expression
Thread ID:0, Value : 494982776
Thread ID:2, Value : 495154121
Thread ID:1, Value : 494781791
In case of ForEach
Thread ID:2, Value : 495154121
Thread ID:1, Value : 494781791
Thread ID:0, Value : 494982776

排他制御

まずは排他制御なしで実行。スレッドを20個用意して、そのそれぞれでsum(初期値:100)から数を引いていくような処理を実行。
計算結果が負にならない時にだけ引き算するようにしているつもりが・・・

using System;
using System.Threading.Tasks;   

class Program
{
    //全スレッドから参照される合計値
    private static int sum = 100;

    static void Main(string[] args)
    {
        //スレッド20個用意して、スレッドの番号を適当に引いていく
        Parallel.For(1, 20, i =>
        {
            for (int j = 0; j < 100; j++)
            {
                //計算結果が負にならないときだけ実行(しているつもり)
                if ((sum - i) > 0)
                {
                    Console.WriteLine("Current sum :  " + sum);
                    Console.WriteLine("Withdraw    : -" + i);
                    sum = sum - i;
                    Console.WriteLine("New sum :  " + sum);
                }
            }
        });

        //計算終了時のsum
        Console.WriteLine("Result : {0}", sum);
    }
}

実行結果

Current sum :  100
Current sum :  100
Withdraw    : -13
New sum :  87
Current sum :  87
Withdraw    : -13
New sum :  74
Current sum :  74
Withdraw    : -13
New sum :  61
Current sum :  61
Withdraw    : -13
New sum :  48
Current sum :  48
Withdraw    : -13
New sum :  35
Current sum :  35
Withdraw    : -13
New sum :  22
Current sum :  22
Withdraw    : -13
New sum :  9
Current sum :  9
Current sum :  100
Withdraw    : -9
New sum :  0
Withdraw    : -5
New sum :  -5
Current sum :  100
Current sum :  100
Withdraw    : -17
New sum :  -22
Withdraw    : -1
New sum :  -23
Withdraw    : -2
New sum :  -25
Result : -25

最終計算結果がマイナスになってしまう。これは変数を参照するときにきちんと排他制御していないために起こるので排他制御を導入する。
クリティカルセクション排他制御をかけてる箇所)は同期オブジェクト(排他制御する際のキーとなるオブジェクト)を使って以下のように書く。
同期オブジェクト自体はなんでもいいっぽい。

Object thisLock = new Object();
lock (thisLock)
{
// Critical code section.
}

これを参考にコードを修正。

using System;
using System.Threading.Tasks;   

class Program
{
    //全スレッドから参照される合計値
    private static int sum = 100;

    static void Main(string[] args)
    {
        //同期オブジェクトの生成
        Object thisLock = new Object();
        //スレッド20個用意して、スレッドの番号を適当に引いていく
        Parallel.For(1, 20, i =>
        {
            for (int j = 0; j < 100; j++)
            {
                lock (thisLock)
                {
                    //計算結果が負にならないときだけ実行
                    if ((sum - i) > 0)
                    {
                        Console.WriteLine("Current sum :  " + sum);
                        Console.WriteLine("Withdraw    : -" + i);
                        sum = sum - i;
                        Console.WriteLine("New sum :  " + sum);
                    }
                }
            }
        });

        //計算終了時のsum
        Console.WriteLine("Result : {0}", sum);
    }
}

実行結果

Current sum :  100
Withdraw    : -1
New sum :  99
Current sum :  99
Withdraw    : -1
New sum :  98
Current sum :  98
Withdraw    : -5
New sum :  93
Current sum :  93
Withdraw    : -5
New sum :  88
Current sum :  88
Withdraw    : -5
New sum :  83
Current sum :  83
Withdraw    : -5
New sum :  78
Current sum :  78
Withdraw    : -5
New sum :  73
Current sum :  73
Withdraw    : -5
New sum :  68
Current sum :  68
Withdraw    : -5
New sum :  63
Current sum :  63
Withdraw    : -13
New sum :  50
Current sum :  50
Withdraw    : -17
New sum :  33
Current sum :  33
Withdraw    : -17
New sum :  16
Current sum :  16
Withdraw    : -9
New sum :  7
Current sum :  7
Withdraw    : -1
New sum :  6
Current sum :  6
Withdraw    : -5
New sum :  1
Result : 1

ちゃんと答えが負にならない範囲で計算が終了する。