前提: 演算器
例えばAND回路とかOR回路なんかをイメージしてほしい。
まずCPUがある
一般にCPUは1つのPCに1つのものをよく見かけるが、複数CPUが乗っていることもある。
コアとは
CPUは多数の演算器が複雑に組み合わさってできているが、この脳みそに当たるものが1つのCPUの中に複数あることがある(というかそのほうが主流)。これがコアだ。
Pipeline
基本的にコア1つに付き1つの作業しか同時にできない。はずであった。しかし、作業内容をCPUが見抜いて、依存関係のない演算同士をばらしてPipelineを組み立て、同時に複数の処理を実行するという離れ業を現代のCPUはやってのける。
ただし、例えば条件分岐では、分岐するまでパイプライン進められなくなる。パイプラインを崩す。
条件分岐予測とか投機的実行
パイプラインが崩れると処理速度が遅くなって困るので条件分岐に対しては条件分岐予測というものがある。
つまり、CPUの謎パワーで条件分岐先を予測して、投機的実行(Pipelineを組み立て分岐先の処理を実行)してしまう。条件分岐判定が終わって予測が間違っていたら処理を差し戻して正しい処理を実行する。
この投機的実行の有用性が揺らぐ事件としてSpectreがある。雑に解説すると、処理の差し戻しが不完全なことで意図しない副作用が起きるというものである。まじでどーすんだこれ。
SMT(同時マルチスレッディング)
Pipelineとは別に、基本的にコア1つに付き1つの作業しか同時にできないという概念を覆すものがある。1つの作業中のCPUをよく観察すると、すべての回路が仕事をしているわけではなかった。遊んでいるところでもう1個仕事できるんじゃないか?例えるならば右手でスマホを操作しながら左手で飲み物を飲めるようなものだ。
いわゆる論理コア。
Intel® Hyper-Threading Technology
SMTの実装例である。1つのコアを2つのコアに見せかける。HTT。Technologyは冗長だから省いてHTと呼んだりハイパースレッディングと呼んだり。
プロセス
一気にソフトウェア面での話になってくる。
実行ファイルを実行する時にOSが生成し管理する。
一般的なOSでは当たり前のようにプロセスは複数作れる。
スレッド
プロセスごとにスレッドを生成できる。プロセスではメモリー空間を共有するのが難しい。スレッドならメモリー空間は共有だ(ただしバグを作り込みやすい)。スレッドの生成コストは普通プロセスの生成コストより小さい。
スレッドプール
プログラミングの技法的な話になってくる。スレッドは確かにプロセスの生成コストより小さいかもしれないがそれでも重い。ループの中で逐次スレッドを生成しては破棄するような使い方をすると遅すぎる。ここで生産者ー消費者モデルというプログラミングの技法を使う。つまり予めいくつかのスレッドを生成しておき休眠させ、必要になったら起こしてデータを渡して処理を行わせ、終わったら次のデータが来るまでまた眠る。これがスレッドプールだ。
非同期
スレッドプールをまともに実装するなんてことができるのは一部のプログラマーだけだ。一般的なプログラマーには無理だし、そうでなくても楽をしたい。生産者ー消費者モデルではなく、ある処理が終わったときに呼び出す処理(callback)を登録しておくという手法を用いるほうがわかりやすい。これが非同期プログラミングだ。
futhre/thenとかasync/awaitとか
非同期プログラミングはスレッドプールを自分で書くよりはマシかも知れないが、プログラムの可読性が落ちる(callback地獄)。そこでライブラリ側でこれをどうにかする手法としてfuture/thenだったり、これをもうすこし可読性を上げて書けるようにするプログラミング言語仕様側のサポートとしてasync/awaitとかが一部のプログラミング言語にはある。
「スレッドと呼ばれるものが2つある問題」
プロセスごとにスレッドがあるのだが、一方でSMTの論理コアのことをスレッドと呼ぶことがある。プロセスごとのスレッドを論理コアにOSが割り当てるのだからThreadingのほうが正しいはずなのだが、どういうわけか論理コアのこともスレッドと呼ぶ。意味がわからない。