1つのコアで複数のスレッドを切り替えながら並行実行する仕組み
マルチスレッドとは、1つのコア(=CPU内で実際に命令を実行する装置)の上で複数のスレッド(=プログラムの中の処理の流れ)を、短い時間ずつ切り替えながら並行して進める仕組みのことです。
身近な例で考えると、1人のレジ係が3つの会計を少しずつ交互にさばくイメージです。1人(1コア)なので本当に同時に処理しているわけではありませんが、すばやく切り替えるため、お客さんからは「3つ同時に進んでいる」ように見えます。これを並行(concurrent)と呼びます。
上のツールで ▶ ボタンを押すと、1コアの上でスレッド A・B・C が順番に実行され、間にコンテキストスイッチ(切替)を挟みながら進む様子を確認できます。
あるスレッドから別のスレッドへ実行を切り替える作業をコンテキストスイッチと呼びます。コンテキストとは「そのスレッドの状態」のことで、具体的には次のようなものを指します。
切替のときにOSがやること:
・退避(save):今動いていたスレッドのレジスタ・プログラムカウンタ(=次に実行する命令の番地)を保存する
・選択:次に実行するスレッドをスケジューラ(順番を決めるOSの機能)が選ぶ
・復元(restore):次のスレッドの状態を読み込み、続きから再開できるようにする
この切替には少しだけ時間がかかり、その間はどのスレッドの仕事も進みません。これをオーバーヘッド(本来の処理以外にかかるムダな時間)と呼びます。切替が細かすぎるとオーバーヘッドが増えて遅くなるため、OSは適度な長さのタイムスライス(各スレッドへの割当時間)で区切ります。上のツールの紫色の区間が、この切替時間にあたります。
マルチスレッドとマルチコアは混同しやすいですが、「本当に同時か、見かけ上同時か」という決定的な違いがあります。
| 項目 | マルチスレッド(1コア) | マルチコア |
|---|---|---|
| 同時に動く処理数 | 1つ(交代で実行) | 物理的に複数 |
| 実現方法 | 時間で切り替える(時分割) | コアを増やす(ハード) |
| 呼び方 | 並行(concurrent) | 並列(parallel) |
| 切替オーバーヘッド | あり(コンテキストスイッチ) | 少ない(各コアが独立) |
「1コアのマルチスレッドは並行(見かけ上の同時実行)、マルチコアは並列(物理的な同時実行)」と整理しておくと混同しません。なお実際のパソコンは「複数コア × 各コアでマルチスレッド」を組み合わせており、両方の良いところを使っています。
並行(concurrent)とは、1つのコアが複数の処理を「時間を小さく区切って交互に」こなすことです。ある瞬間に動いているのは1つだけですが、切り替えがあまりに速いため、外から見ると「同時に動いている」ように感じます。
なぜ「同時に見える」かというと、切り替えの速さが人間の感覚をはるかに超えているからです。1秒間に何千回も切り替えが起きると、人間には「全部が同時に進んでいる」としか感じられません。映画のフィルムが1秒24枚の静止画なのに動いて見えるのと同じ原理です。
一方、並列(parallel)は複数のコアが文字通り同時に別々の処理を動かすことで、物理的に本当の同時実行です。
・並行:1人の人間が2つの仕事を「少しずつ交互に」こなす
・並列:2人の人間がそれぞれ1つの仕事を「完全に同時に」こなす
マルチスレッドで気をつけなければならないのが競合状態(レースコンディション)です。これは、複数のスレッドが同じデータを同時に読み書きしようとして、結果がおかしくなる現象のことです。
なぜ起きるのかというと、「読む→変える→書く」という操作が複数ステップになっているからです。スレッドAが「残高を読んで+500した値を書こうとしている途中」でスレッドBが割り込んで書き換えると、AもBも古い値をもとに計算してしまい、片方の変更が消えてしまいます。
・スレA:残高1000を読む → 1500にして書こうとする(途中で切替)
・スレB:残高1000を読む → 800にして書く
・スレA:1500を書く → Bの「−200」がなかったことになる
これを防ぐのが排他制御(ミューテックス・ロック)です。「今Aが使っているからBは入ってこないで」とドアに鍵をかけるイメージです。鍵をかけた間は他のスレッドは外で待ち、Aが終わって鍵を開けてから次が入ります。これで1度に1スレッドだけが共有データを操作できるため、データが壊れなくなります。