目次

概要

Ver. 4.0

マルチコア CPU の普及に伴って、並列処理の重要性が増しています。 この時代背景に合わせるかのように、.NET Framework 4で並列処理用のライブラリが追加されました。

Parallel クラス

まずは、制御フロー(「制御フロー」参照)の並列化です。 Parallel クラス(System.Threading.Tasks 名前空間)を使うことで、 通常の for 文や foreach 文に非常に似た書き方で並列処理を行えます。

Parallel クラスは Invoke、For、ForEach の3つの静的メソッドを持っています。

Parallel クラスを使った制御フローの並列化
メソッド 逐次処理版 並列処理版
Invoke
A();
B();
C();
Parallel.Invoke(A, B, C);
For
for (int i = 0; i < N; i++)
{
    Console.WriteLine(i * i);
}
Parallel.For(0, N, i =>
{
    Console.WriteLine(i * i);
});
ForEach
var data = Enumerable.Range(0, N);
 
foreach (var x in data)
{
    Console.WriteLine(x * x);
}
var data = Enumerable.Range(0, N);
 
Parallel.ForEach(data, x =>
{
    Console.WriteLine(x * x);
});

逐次処理とほとんど同じ書き方で並列処理ができます。

ただし、複数のスレッドから同じデータを読み書きする場合には「排他制御」が必要なので注意してください。 例えば、以下のような処理は、単に foreach 文を Parallel.ForEach メソッドに置き換えるだけでなく、 ロックが必要です。

var data = Enumerable.Range(0, N);
 
var sum = 0;
foreach (var x in data)
{
    sum += x;
}
Console.WriteLine(sum);

以下のように、sum += x の部分にロックを掛けます。

var data = Enumerable.Range(0, N);
 
var sum = 0;
Parallel.ForEach(data, x =>
{
    lock (data) sum += x;
});
Console.WriteLine(sum);

ロック自体がそれなりにオーバーヘッドのかかる処理なので、 この例の場合、並列化するとかえって遅くなる可能性があります。

Parallel LINQ

LINQ」 に対する並列化の仕組みも用意されています。 System.Linq 名前空間に ParallelEnumerable というクラスが追加されていて、 このクラスで定義されている AsParallel 拡張メソッドを使えば、LINQ クエリを並列化できます。 (データ ソースに対して .AsParallel() を付けるだけです。)

var data = Enumerable.Range(0, N);
var sqSum = data.AsParallel().Sum(x => x * x);
Console.WriteLine(sqSum);

必要な「排他制御」は適宜ライブラリ内で行ってくれるので、 こちらはロックが不要です。 なので、データ ストリーム(「ストリームとパイプライン」参照)に対する並列処理は、 Parallel クラスを使うよりも、こちらを使う方がおすすめです。

ただし、多少の工夫が必要な場合もあります。 例えば、1つ前の要素を参照したいというような場合、 以下のように書いてしまいがちです。

// 1つ前の値を保存しておく
var prev = data.First();
var max = int.MinValue;
foreach (var x in data.Skip(1))
{
    // 階差の最大値
    max = Math.Max(x - prev, max);
    prev = x;
}

並列化したい場合、必ずしも順序の保証がないので、prev = x; では「1つ前の要素を保存」という処理になりません。 以下のような工夫が必要になります。

// 1項ずらしたデータ ストリームと Zip
var difference = data.Zip(data.Skip(1), (i, j) => j - i);
// そのあと、AsParallel
var max = difference.AsParallel().Max();

更新履歴

ブログ