見出し画像

[C#] マルチスレッドの基礎を理解する

非同期処理やマルチスレッドとはなんぞやという人のためにシンプルなまとめ

スレッド

スレッドは「プログラムの処理単位」みたいな言われ方をするけど、処理単位って言われてもあまりピンとこないので、プログラムを見たほうが理解しやすい。

画像1

このプログラムでif文がy==0の方に分岐したとするとこのプログラムの実行順序は図の矢印みたいな流れになることがわかると思う。
この矢印のような実行の一連の流れがスレッドだ。こんな感じでプログラムが開始してからの最後まで続くスレッドは特にメインスレッドと呼ばれる。途中で関数に寄り道してても一本の処理として連続してるので同じスレッドだ。

マルチスレッドで動かす

マルチスレッドは言葉の通り複数のスレッドを扱う処理だ。
C#でマルチスレッド処理を行う最も簡単な方法はTask.Run()を使う方法だ。

画像2

スレッドの説明で出したコードと違うのはSample()関数を呼び出すところのみで、Task.Run()の引数に別スレッドで処理させたい関数を渡してあげるだけだ。
こうするとTask.Runを読んだところから別のスレッド(赤い矢印)が生成されて、処理が分岐する。これの便利なところはメインスレッドと生成されたスレッドが同時並行的に処理が進むことだ。

await / async

非同期処理を学ぼうとすると必ずawaitやasyncといった言葉を見かける。
awaitとasyncの使い方を見てみよう。

画像3

少し複雑になったのがわかると思う。
まず先ほどと同様にTask.RunでSample1関数を別スレッドで実行している(Sample1の呼び出しで名前間違ってるのは許して...)。そして、Sample1の中でさらにTask.Runで新たにスレッドを作成してSample2を実行している。ここで注意するのはawait Task.Runのところで赤いスレッドがMain関数に戻って終了している点だ(図の※位置)。そしてSample2の実行が終わると処理はSample1のawait 位置に返り、await以降の行の処理が実行される。

asyncはawaitを中で使う関数につける修飾子だ。関数が非同期処理を行なっていることを表すためにつける。

スレッドセーフ

次に、スレッドセーフという言葉も理解しておかなければならない言葉の一つだ。
以下の例を見てもらいたい。

画像4

この例ではThreadAとThreadBという処理をそれぞれ別のスレッドで同時並行的に処理を行なっていることがわかると思う。
しかし、それぞれの処理でこのクラスのメンバ変数memberに対して代入や読み取りなどの処理を行なった場合どうなるだろうか?答えは「それぞれの関数がプログラマの期待する通りに動作するかわからない」だ。
なぜなら、別々のスレッドで動いている処理がそれぞれのタイミングでmemberに対して処理を並行に行うから。ThreadAの処理でmemberの値が変わって欲しくない時にThreadBがmemberに対して値の変更を行うといったことがあるかもしれない。別スレッドの処理が行われるタイミングはプログラマにはわからないため、動作が保証できないのだ。

ThreadAやThreadB内で宣言されたローカル変数localAとlocalBはお互いのスレッドからは参照できないため、予期しない動作を引き起こすことはない。このような変数はスレッドセーフと呼ばれる。対して、memberのようなお互いのスレッドから見える変数などはスレッドセーフではないと言える。

マルチスレッド処理は便利だが、思わぬところで予期しない動作を引き起こす可能性があるため、排他制御という処理を行なって、スレッドセーフ性を保証することが望ましい。排他制御に関しては今回は触れない。

まとめ

マルチスレッド処理というのは、スレッドを複数用いて並行に処理を行うことができる便利な処理だ。しかし、プログラマはマルチスレッド化によって引き起こされる弊害についてもよく理解して配慮しなければならない。

非同期処理とはスレッドを複数動かすという点でほとんどマルチスレッドと同じ意味だ。ただし、非同期処理という言葉は別のスレッドでの処理を待つことなく、メインスレッドの処理を進めていくという使い方をする時に用いられる。

この記事では基礎についてしか触れていないが、もっと詳しく知るには「排他制御」、「Task」などについてもっと調べてみるといいだろう。

この記事が気に入ったらサポートをしてみませんか?