今回はC#のスレッド処理について理解を深めてみようと思う。

並行して処理が行われるのはすぐにイメージできるが、プログラムにしてみると少しイメージが薄れてしまう。

なので、簡単なスレッド処理を動かし、それをイメージ図にしてみようと思う。

Parallel クラスを使ってみる

まずはこちらのサイトに掲載されていたプログラムの内容を少し書き換えて実行してみる。

マルチスレッド – C# によるプログラミング入門

以下のプログラムでは「Parallel.For~」より下の部分が並列で処理される。

上記を実行した結果はこのようになる。

まずは「Parallel.For~」を過ぎた部分から各スレッド処理が開始される。今回の場合、スレッドの数を3に指定しているのでスレッドは3つ作成される。「~の処理が始まりました」の部分で各スレッド処理が開始されていることが分かる。

1つのスレッド内ではFor文が処理され、コンソールにカウントが表示されるようになっている。今回はこのスレッドが3つ同時に動いている。

上記の結果からはスレッド内の処理が終了したものから「~の処理が終了しました」と表示されているのが分かる。今回はThreadID 0が一番最初に終了し、次にThreadID 1、その次にThreadID 2のスレッドが終了している。この結果については毎回異なってくる。

上記の場合、全てのスレッドが終了するまでは自動的に待つようになっている。

イメージ図にしてみるとこのような感じ↓

以上がC#のスレッド処理における最も基本的な考え方である。

スレッドプールを作成してみる

次にスレッドプールを作成してスレッドを管理してみる。上記のサイトで紹介されていた素因数分解を行うプログラムを少し修正してみたのがこちら。

上記を実行するとこのようになる↓

簡単に内容を解説する。まず、素因数分解したい数値を入力するとメインスレッドの最初のIF文でelseのほうに処理が入り、sThreadStateがState.runningとなる。ここでサブスレッドが新規に作成されて素因数分解を実行するプログラムが呼ばれる。

このとき、メインスレッドとサブスレッドは同時に動いており、メインスレッドは入力待ち状態、サブスレッドは計算処理を実行している。

計算途中で入力があった場合、メインスレッド内でsThreadStateがState.waitとなり、計算が中断される。

上記をまとめるとメインスレッドのスレッドプールでサブスレッドは管理され、サブスレッドは入力があった場合に計算処理を実行するスレッドになっている。メインスレッドとサブスレッドは別に動いているため、サブスレッド側が計算途中であってもメインスレッドの方で入力値を受け付けることができる。

これがC#のスレッド処理におけるスレッドプールの考え方である。

Monitorクラスで排他制御をしてみる

次に複数のスレッドが同じ変数にアクセスする場合の排他制御(ロック)を実施してみます。使用するのは「System.Threading.Monitor」クラスです。

以下のプログラムを実行すると、Monitor.Enter ~ Monitor.Exitまでがロックがかけられた状態になります。ロックがかけられている場合、他のスレッドはこの間の処理を実行することができません。

実行した結果がこちらになります。numがインクリメントされた結果と、ThreadNum(スレッド数)× LoopNum(ループの回数)の結果が同じになります。ロックがかけられていない場合、numを各スレッドのタイミングでインクリメントしてしまうので結果に誤差が出てきます。

イメージにするとこのような感じ↓

ロック文で排他制御をしてみる

上記のMonitorクラスで実施した排他制御はロック文でもっとシンプルに記述することができる。ロック文で記述するときは以下のようにする。

 

Taskクラスを用いる

Taskクラスを用いても今までの処理を再現することができる。私はTaskクラスを用いることが少ないので今回は省略する。

以下のサイトの「Taskクラス」の項目のところに詳しい解説が書かれているのでそちらをご参照ください。

・[非同期処理] [雑記] スレッド プールとタスク

1枚の画像を複数のスレッドで読み込んでみる

1枚の画像を複数のスレッドで読み込んでみるとどうなるだろうか?ここではそれについて検証してみる。

まずはロックをかけない状態で「src1.png」という画像ファイルを複数のスレッドで読み込んでみる。

ここでのプログラムは画像をグレースケールで読み込んで、そのまま「dst1.png」として保存するというプログラムになっている。

上記のプログラムを実行してみると大体は途中で以下のようなエラーが発生する。

つまりは1枚の画像を複数スレッドで同時に読み込もうとしているのがよくないらしい。よって、これにロック文を追加することで解決してみようと思う。ロック文を追加してみたプログラムがこちら。

ロック文を追加して実行すると以下のように最後までエラーを発生させずにプログラムを終了することができる。つまりは複数のスレッドが画像の読み込みを同時に行わなくなるのでエラーが発生しなくなる。

複数のスレッドで画像処理を行いたいときなどはこのようなこともやりたくなったりするので、そういう場合は同じファイルを同時に読み込まないようにロック文を追加するとよさそうだ。

 

以上までの項目がC#でスレッド処理を実行するときに基本となる考え方である。