😎

炊飯ボタンを押したなら風呂に入れ! (並行プログラミング 超入門)

に公開

誰のための記事か

  • 並行プログラミングに入門した人

はじめに

プログラミングをする上で避けては通れないものの一つが並行プログラミングです。
並行プログラミングを活用することで、時間効率がよくなる可能性があります。

もし並行プログラミングという響きに興味が湧いたのであれば、どういったモノなのかの肌感覚が掴めると思います!

並行プログラミングとは?

並行プログラミングとは、複数のプログラムのまとまりを同時に実行するプログラミング手法です。

なぜそのような機能が現代のプログラミング言語には備わっているのでしょうか。

ノーマルなプログラムはコードの上から順番に処理されます (逐次性)。上の行のプログラムは下の行のプログラムより先に実行され、完全に処理が完了してから下の行のプログラムが実行されます。これはとてもシンプルで分かりやすいと思います。しかし大きな弱点を抱えています。

それは「暇な時間に弱い点」です。

例を挙げてみましょう。

炊飯器とシャワー

Aさんはお腹が空いたので炊飯器でご飯を炊いてお茶碗によそって食べたいと考えています。加えて、Aさんは1日の仕事で汗だくなので、シャワーも浴びたいと考えています。

下記がAさんの日記です。

  1. お腹が空いたので炊飯ボタンを押した (21:00)
  2. 炊飯器の前でご飯が炊けるのをジーッと眺めた (21:00 ~ 22:00) ← 暇で可哀想...
  3. ご飯が炊けたのでお茶碗によそって食べ始めた (22:00 ~ 22:30)
  4. シャワーを浴びた (22:30 ~ 23:00)

恐らく「なんて非効率な奴なんだ」と思ったことでしょう。

炊飯ボタンを押したらすぐにシャワーを浴びに向かう方が時間効率が良いですよね?

プログラミングでも全く同じです。ある時間のかかる処理が実行されているとき、律儀に完了を待っていると時間がもったいないのです。これこそが「暇な時間に弱い」ということです。

下記が時間の勿体無い例です。

疑似コード
fn foo() {
  let urls = [
    "https://example.com/1", // レスポンスまでに3秒かかる
    "https://example.com/2", // レスポンスまでに2秒かかる
    "https://example.com/3" // レスポンスまでに5秒かかる
  ];

  for url in urls {
    let response = httpRequest(url); // 各 URL を取得して HTTP Request を投げる
    print(response);
  }
}

HTTP Request を投げるとレスポンスが返ってくるまでの数秒間は何もせずに待っています。おおよそ「10秒(3+2+5秒)」程度かかる計算になります。先ほどのAさん現象と同じですね。

そこで役に立つのが「並行プログラミング」です🎉

並行プログラミングを使うと、あるプログラムのまとまりを同時に走らせることができます。簡単な例だと、単にスレッドを立てることで並行プログラミングが可能になります。

上記の例を並行プログラミングで実装するとどうなるでしょうか。

疑似コード
fn foo() {
  let urls = [
    "https://example.com/1", // レスポンスまでに3秒かかる
    "https://example.com/2", // レスポンスまでに2秒かかる
    "https://example.com/3" // レスポンスまでに5秒かかる
  ];

  for url in urls {
    // 新しくスレッドを立てて、並行に処理をする
    new_thread({
      let response = httpRequest(url); // 各 URL を取得して HTTP Request を投げる
      print(response);
    });
  }
}

今度は HTTP Request を投げてからレスポンスを受け取るまで待たずに、次の HTTP Request を投げます。これによって無駄な待ち時間が発生しないので、おおよそ5秒で完了するはずです。(3,2,5秒の中で最も時間のかかるものに依存する)

実際にスレッドを立ててプログラムを書く時は、各 thread の処理が完了するのを待つような処理を入れなければなりませんが、雰囲気だけ掴めれば良いと思うのでそのあたりは割愛します。

闇雲に並行にすれば良いわけではない

先ほどの例では、スレッドというものを立てることで大きく時間短縮することができるということが分かりました。しかし上手く刺さらない例があります。

計算ドリルと漢字ドリル

Aさんは前回の反省を元に、時間のかかるものは同時にやると早くなると学習しました。

今日はAさんの学校の宿題で、計算ドリルと漢字ドリルが配られました。計算ドリルは20分、漢字ドリルは30分かかるだろうと予測したので、並行にやれば早く終わるかもしれないと閃きました。

下記がAさんの日記です。

  1. 計算ドリルを10分やる
  2. 漢字ドリルを10分やる
  3. 計算ドリルを10分やる (計算ドリル完了)
  4. 漢字ドリルを20分やる (漢字ドリル完了)

またもや皆様の頭には「?」が沢山浮かび上がったでしょう。結局合計してみると50分かかっています。
なぜ早くならなかったのでしょうか。それは先ほどと違って「暇な時間がない」ためです。

両方に着手したとしても、捌く量自体には変わりはありません。ですので、時間がかかるから両方着手すれば早くなるんじゃないのかと早まってはなりません。

ですが、宿題を早く終わらせる方法が実はあります。友達のBさんに宿題を手伝ってもらえば良いのです。そうすれば宿題を2人で分け合えば良いので、おおよそ25~30分くらいで終わるでしょう。

実は先ほどの注釈に「並列」という言葉をチラッと出しましたが、これこそが「並列」です。

AさんとBさんによって並列で宿題をするのです。
そして、先ほどAさんはお米の炊飯と並行でシャワーを浴びたのです。

要するに、ある時間の点を抽出した時、複数個のタスクが同時に処理されているなら「並列」です。
また、ある時間範囲の中で複数のタスクが実行されている状態が「並行」です。

余談ですが、並行でできることは並列でもできます。(計算資源が無駄になりますが)
炊飯器とシャワーの例を並列で行うなら、Bさんに炊飯ボタンを押してもらい、そのまま炊飯器の前でボケーっと待ってもらい、炊けたらご飯を茶碗によそいます。その間、Aさんはシャワーに入るといった形です。

まとめ

並行プログラミングが何者なのかが分かったと思います。
まとめると、

  • 待ち時間が支配的なら並行で
  • 計算パワーが必要なら並列で

ご精読ありがとうございました!GitHub のフォローも待ってます🥺
https://github.com/tomoikey

Discussion