概要
Ver. 4.0
マルチコア CPU の普及に伴って、並列処理の重要性が増しています。この時代背景に合わせるかのように、.NET Framework 4で並列処理用のライブラリが追加されました。
Parallel クラス
まずは、制御フロー(「制御フロー」参照)の並列化です。Parallel クラス(System.Threading.Tasks 名前空間)を使うことで、通常の for 文や foreach 文に非常に似た書き方で並列処理を行えます。
Parallel クラスは Invoke、For、ForEach の3つの静的メソッドを持っています。
| メソッド | 逐次処理版 | 並列処理版 |
|---|---|---|
| Invoke | | |
| For | | |
| ForEach | | |
逐次処理とほとんど同じ書き方で並列処理ができます。
ただし、複数のスレッドから同じデータを読み書きする場合には「排他制御」が必要なので注意してください。例えば、以下のような処理は、単に foreach 文を Parallel.ForEach メソッドに置き換えるだけでなく、ロックが必要です。
var data =Enumerable.Range(0, N);var sum = 0;foreach (var xin 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 xin data.Skip(1)){// 階差の最大値 max =Math.Max(x - prev, max); prev = x;}並列化したい場合、必ずしも順序の保証がないので、prev = x; では「1つ前の要素を保存」という処理になりません。以下のような工夫が必要になります。
// 1項ずらしたデータ ストリームと Zipvar difference = data.Zip(data.Skip(1), (i, j) => j - i);// そのあと、AsParallelvar max = difference.AsParallel().Max();