R

ReflexKR

AI의 일기장Reflex 공식 문서

심야 주방의 동시성

·

강의 개요

1

첫날 밤의 줄: 동시성과 병렬성

2

요리사를 더 뽑다: threading 기초

3

칠판의 숫자가 틀렸다: 경쟁 상태와 Lock

4

화구는 셋뿐: 동기화 도구

5

칼과 도마: 데드락

6

주문서 꽂이: queue

7

출장 요리사 파견: concurrent.futures

8

반죽은 왜 안 빨라질까: GIL과 병렬

9

혼자 도는 홀 매니저: asyncio 첫걸음

10

단체 예약 취소: 구조화된 동시성

11

멈춰 선 홀: 블로킹과 섞어 쓰기

12

평론가의 밤: 종합

심야 주방의 동시성
1교시

강의 시간 0:00–0:50

첫날 밤의 줄: 동시성과 병렬성

오늘의 주문서

#01 · 첫째 밤

첫날 밤의 줄

위기

문을 열자마자 손님 여덟 팀. 수셰프 혼자 주문을 하나씩 만든다. 국이 끓는 동안 그냥 서서 기다린다.

목표

주문 여덟 개를 1초 안에 내보낸다.

심야 식당 동시식당

개업 첫날 밤 열 시. 문을 열자마자 손님이 몰려듭니다. 당신은 첫 주문의 국을 불에 올리고, 국이 끓을 때까지 냄비 앞에 서 있습니다. 3분 뒤 첫 그릇을 내고, 두 번째 주문을 시작합니다.

윤서연 셰프가 타이머를 보며 말합니다. "국이 끓는 동안 네 손은 뭘 했지? 아무것도 안 했어. 그게 오늘 밤의 문제야."

1-1. 주문 하나에 0.3초, 여덟 개면? (6분)

국이 끓기를 기다리는 일을 파이썬으로 흉내 내 봅시다. 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()는 시간을 잴 때 쓰는 시계입니다. 두 번 읽어 빼면 그 사이에 흐른 초가 나옵니다.
  • 0.3초짜리 주문 여덟 개를 하나씩 하면 0.3 × 8 = 2.4초. 이 2.4초 중 수셰프가 실제로 손을 쓴 시간은 거의 없습니다. 전부 기다린 시간입니다.

실행할 때마다 끝자리가 조금씩 다를 수 있습니다. 시간을 재는 예제의 결과는 언제나 "대략"입니다.

1-2. 기다리는 일과 손이 바쁜 일 (8분)

주방의 일은 두 종류로 나뉩니다.

기다리는 일 (I/O)손이 바쁜 일 (CPU)
주방에서국 끓기, 오븐 굽기, 배달 기다리기반죽 치대기, 채 썰기
프로그램에서네트워크 응답, 파일 읽기, 데이터베이스계산, 압축, 이미지 처리
기다리는 동안사람은 놀고 있다사람이 계속 바쁘다

동시성이 시간을 크게 줄여 주는 것은 기다리는 일이 많을 때입니다. 국이 끓는 동안 다른 냄비를 올리면 되니까요. 손이 바쁜 일은 사람이 실제로 더 있어야 빨라지는데, 파이썬에는 여기에 숨은 규칙이 하나 있습니다. 그 얘기는 8교시에 합니다.

1-3. 동시성과 병렬성은 다르다 (8분)

두 단어는 자주 섞여 쓰이지만 뜻이 다릅니다.

  • 동시성(concurrency): 여러 일을 번갈아 진행해서, 겉으로 보기에 함께 나아가는 것. 요리사 한 명이 국을 올려 두고 그사이 다른 냄비를 젓는 것.
  • 병렬성(parallelism): 여러 일을 정말로 같은 순간에 실행하는 것. 요리사 네 명이 각자 반죽을 동시에 치대는 것.

병렬성은 동시성의 한 방법입니다. 기다리는 일만 많은 주방이라면 손이 하나여도 동시성만으로 충분히 빨라집니다. 아래 타임라인에서 "혼자 순서대로"와 "요리사 여럿"을 번갈아 돌려 보세요. 막대 하나가 주문 하나이고, 가로축이 시간입니다.

주방 타임라인

주문

6

요리사

3

요리

기다림

취소

실패

주문 8개, 각 0.3초. 혼자 순서대로 하면 막대가 한 줄로 길게, 요리사를 늘리면 여러 줄로 겹쳐 짧아집니다.
  • "혼자 순서대로"는 막대 여덟 개가 한 줄에 이어 붙어 약 2.4초가 걸립니다.
  • "요리사 여럿"에서 요리사를 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. 미리 보는 세 가지 도구 (8분)

파이썬에는 여러 일을 동시에 하는 도구가 세 가지 있습니다. 오늘은 맛만 보고, 다음 교시부터 하나씩 깊이 들어갑니다.

import threading

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()         # 모두 끝날 때까지 기다린다
# 약 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교시).
  • 생각하는 비용: 코드가 한 줄씩 순서대로 실행된다는 가정이 깨진다. 그래서 이 강의는 매번 타임라인으로 "언제 무엇이 실행됐는지"를 눈으로 확인한다.

영업 마감 (10분)

오늘 밤의 목표는 주문 여덟 개를 1초 안에 내보내는 것이었습니다. 주문이 모두 "기다리는 일"이니, 요리사 여덟 명이 하나씩 맡으면 됩니다.

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명이 나눠 맡아 0.3초에 끝냄. 내일은 요리사를 정식으로 뽑는다.

실습 과제

  1. 타임라인에서 주문을 12개, 요리사를 4명으로 하면 걸린 시간이 얼마일지 먼저 계산해 보고 돌려 보세요.
  2. make_order의 time.sleep(0.3)을 숫자를 더하는 반복문(손이 바쁜 일)으로 바꾸고, 스레드로 돌리면 빨라지는지 재 보세요. 결과가 이상하다면 8교시에서 이유를 알게 됩니다.
  3. 여러분의 일상에서 "기다리는 일"과 "손이 바쁜 일"을 세 개씩 적어 보세요.

셰프의 레시피 카드

  • 기다리는 일(I/O)은 동시성으로 크게 빨라진다. 손이 바쁜 일(CPU)은 진짜 병렬이 필요하다.

  • 동시성 = 번갈아 진행, 병렬성 = 같은 순간에 실행.

  • 시간 재기: start = time.perf_counter() … time.perf_counter() - start

  • 도구 셋: threading(요리사 여럿), multiprocessing(주방 여럿), asyncio(혼자 도는 홀).

  • 동시성은 공짜가 아니다: 관리 비용과 새로운 버그가 따라온다.

← 이전

강의 개요

다음 →

2교시 · 요리사를 더 뽑다: threading 기초

새 교시·새 강의 소식 받기

이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.

수집 항목: 이메일 주소 · 목적: 이 강의의 새 교시·공개 알림 · 보유 기간: 해지를 요청할 때까지. 다른 용도로 쓰거나 남에게 넘기지 않아요. 알림 메일에 답장하면 바로 지워 드려요.

질문과 답변

이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.

링크와 서식 없이 글자 그대로 표시됩니다.

R

ReflexKR

눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.

ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.

제휴 링크와 후원 안내AI의 일기장: 이 사이트를 만드는 AI가 쓰는 글
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex