R

ReflexKR

Reflex公式ドキュメント

深夜厨房の並行処理

·

講座の概要

1

初日の行列:並行と並列

2

料理人を増やす:threadingの基本

3

黒板の数が合わない:競合状態とLock

4

コンロは3つだけ:同期の道具

5

包丁とまな板:デッドロック

6

伝票差し:queue

7

出張料理人:concurrent.futures

8

生地はなぜ速くならない:GILと並列

9

一人で回すホール係:asyncioの第一歩

10

団体予約のキャンセル:構造化された並行処理

11

止まったホール:ブロッキングと組み合わせ

12

評論家の夜:総まとめ

深夜厨房の並行処理
1限目

時間 0:00–0:50

初日の行列:並行と並列

今夜の注文書

#01 · 一日目の夜

初日の行列

ピンチ

店を開けたとたんに客が8組。副料理長が一人で注文をひとつずつ作る。スープが煮えるあいだ、ただ突っ立って待っている。

目標

8つの注文を1秒以内に出す。

深夜食堂 ドンシ

開店初日の夜10時。扉を開けたとたん、客がどっと押し寄せます。あなたは最初の注文のスープを火にかけ、煮え立つまで鍋の前に立っています。3分後に1杯目を出し、2つ目の注文に取りかかります。

ユン・ソヨンシェフがタイマーを見ながら言います。「スープが煮えてるあいだ、その手は何してた? 何もしてないよね。それが今夜の問題」

1-1. 注文ひとつに0.3秒、8つなら? (6分)

スープが煮えるのを待つ仕事を、Pythonでまねてみましょう。time.sleep(0.3) は「0.3秒間なにもせずに待つ」という意味です。3分を0.3秒に縮めたと考えてください。

import time

def make_order(n):
    time.sleep(0.3)   # スープが煮えるのを待つ
    return f"#{n}"

start = time.perf_counter()
done = [make_order(n) for n in range(1, 9)]
print(f"{time.perf_counter() - start:.2f}s", done)
# 2.40s ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8']
  • time.perf_counter() は時間を測るための時計です。2回読んで引き算すると、そのあいだに経った秒数が出ます。
  • 0.3秒の注文8つをひとつずつこなすと、0.3 × 8 = 2.4秒。この2.4秒のうち、副料理長が実際に手を動かしていた時間はほとんどありません。すべて待ち時間です。

実行するたびに下の桁は少しずつ変わることがあります。時間を測る例の結果は、いつでも「だいたい」です。

1-2. 待つ仕事と、手がふさがる仕事 (8分)

厨房の仕事は2種類に分かれます。

待つ仕事 (I/O)手がふさがる仕事 (CPU)
厨房ではスープを煮る、オーブンで焼く、配達を待つ生地をこねる、千切りにする
プログラムではネットワークの応答、ファイルの読み込み、データベース計算、圧縮、画像処理
待っているあいだ人は手が空いている人はずっと忙しい

並行処理で時間が大きく縮むのは、待つ仕事が多いときです。スープが煮えているあいだに別の鍋をかければいいのですから。手がふさがる仕事は実際に人手を増やさないと速くなりませんが、Pythonにはここに隠れたルールがひとつあります。その話は8限目でします。

1-3. 並行と並列は違う (8分)

この2つの言葉はよく混同されますが、意味が違います。

  • 並行処理(concurrency):複数の仕事をかわるがわる進めて、外から見ると一緒に進んでいるように見えること。料理人一人がスープを火にかけておき、そのあいだに別の鍋をかき混ぜること。
  • 並列処理(parallelism):複数の仕事を本当に同じ瞬間に実行すること。料理人4人がそれぞれ生地を同時にこねること。

並列処理は並行処理を実現する方法のひとつです。待つ仕事ばかりの厨房なら、手がひとつでも並行処理だけで十分速くなります。下のタイムラインで「一人で順番に」と「料理人を増やす」を切り替えながら回してみてください。バー1本が注文ひとつで、横軸が時間です。

厨房タイムライン

注文

6

料理人

3

調理

待ち

キャンセル

失敗

注文8つ、それぞれ0.3秒。一人で順番にやるとバーが1列に長く並び、料理人を増やすと何列にも重なって短くなります。
  • 「一人で順番に」では、バー8本が1列につながって約2.4秒かかります。
  • 「料理人を増やす」で料理人を3人にすると3列が重なって約0.9秒、8人なら約0.3秒です。
  • この実習台は絵ではありません。サーバーで本物のスレッドが time.sleep し、各注文が実際に始まって終わった時刻を記録して描いたものです。

1-4. 料理人を増やすとどれだけ速くなる? (5分)

注文がすべて同じ長さなら、かかる時間は「一人の料理人が受け持つ最大の注文数 × 注文ひとつの時間」です。

import math

def estimate(orders, cooks, each=0.3):
    rounds = math.ceil(orders / cooks)   # 一人の料理人が受け持つ最大の注文数
    return round(rounds * each, 2)

for cooks in (1, 3, 4, 8, 16):
    print(cooks, estimate(8, cooks))
# 1 2.4
# 3 0.9
# 4 0.6
# 8 0.3
# 16 0.3
  • 料理人3人なら 8 ÷ 3 を切り上げて3回、だから0.9秒です。上のタイムラインで見た数字と同じです。
  • 料理人が注文の数より多くなっても、それ以上は速くなりません。注文ひとつの時間(0.3秒)が底です。
  • 注文の長さがまちまちなら、いちばん長い注文が底になります。仕事をどれだけ分けても、いちばん長い一切れより早く終わらせることはできません。

1-5. 3つの道具を先取り (8分)

Pythonには、複数の仕事を同時にこなす道具が3つあります。今日は味見だけして、次の限目からひとつずつ深く掘り下げます。

import threading

threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
    th.start()        # 料理人8人が同時にスタート
for th in threads:
    th.join()         # 全員が終わるまで待つ
# 約0.30秒
import asyncio

async def make_order_async(n):
    await asyncio.sleep(0.3)   # 待っているあいだに別の注文を見る
    return f"#{n}"

async def main():
    return await asyncio.gather(*(make_order_async(n) for n in range(1, 9)))

print(asyncio.run(main()))
# ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8']  (約0.31秒)
道具厨房でいえば向いている仕事限目
threading同じ厨房に料理人を何人も待つ仕事、古いコード2〜7
multiprocessing冷蔵庫まで別々の支店の厨房手がふさがる仕事(CPU)8
asyncio一人で何十卓も回すホールマネージャー待つ仕事がとても多いとき9〜11

シェフのひとこと

料理人を増やせば速くなる、それは本当。ただし厨房が騒がしくなる。二人が同じ黒板に同時に数字を書いたらどうなると思う? それは3限目に自分の目で見られるよ。

1-6. 並行処理はタダじゃない (5分)

料理人を増やすにはコストがかかります。

  • 管理のコスト:スレッドを作ったり順番を入れ替えたりするのにも時間がかかる。注文がとても短いと、かえって遅くなることもある。
  • 新しいバグ:同じ材料を二人が同時にいじると結果が狂う(3限目)。互いに相手の持っている道具を待つと厨房が止まる(5限目)。
  • 考えるコスト:「コードは1行ずつ順番に実行される」という前提が崩れる。だからこの講座では毎回タイムラインで「いつ何が実行されたか」を目で確かめる。

閉店後 (10分)

今夜の目標は、8つの注文を1秒以内に出すことでした。注文はすべて「待つ仕事」なので、料理人8人がひとつずつ受け持てばいいわけです。

start = time.perf_counter()
threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
    th.start()
for th in threads:
    th.join()
print(f"{time.perf_counter() - start:.2f}s")
# 0.30s

シェフがタイマーを止めます。「2.4秒から0.3秒。初日にしては悪くないね。でも料理人が8人になったら、誰が何個作ったかは誰が数えるの?」

営業日誌 — 一日目の夜。注文8件がすべて待つ仕事だったので、料理人8人で手分けして0.3秒で完了。明日は料理人を正式に雇う。

練習問題

  1. タイムラインで注文を12、料理人を4人にすると何秒かかるか、先に計算してから回してみましょう。
  2. make_order の time.sleep(0.3) を数を足し続けるループ(手がふさがる仕事)に置き換え、スレッドで回すと速くなるか測ってみましょう。結果がおかしいと思ったら、その理由は8限目でわかります。
  3. あなたの日常の中の「待つ仕事」と「手がふさがる仕事」を3つずつ書き出してみましょう。

シェフのレシピカード

  • 待つ仕事(I/O)は並行処理で大きく速くなる。手がふさがる仕事(CPU)には本当の並列が必要。

  • 並行 = かわるがわる進める、並列 = 同じ瞬間に実行する。

  • 時間の測り方:start = time.perf_counter() … time.perf_counter() - start

  • 道具は3つ:threading(料理人を何人も)、multiprocessing(厨房をいくつも)、asyncio(一人で回すホール)。

  • 並行処理はタダじゃない:管理のコストと新しいバグがついてくる。

← 前へ

講座の概要

次へ →

2限目 · 料理人を増やす:threadingの基本

新しい授業・講座のお知らせを受け取る

この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。

収集項目:メールアドレス・目的:この講座の新しい授業・公開のお知らせ・保有期間:解除のご依頼まで。他の目的には使わず、第三者にも提供しません。お知らせメールに返信いただければすぐに削除します。

質問と回答

この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。

リンクや書式なしで、そのままの文字で表示されます。

R

ReflexKR

触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。

ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する

アフィリエイトと支援について
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex