일하는 사람을 늘리면 언제나 더 빨리 끝날까요? 계산대가 하나뿐인 가게에서는 점원을 몇 명 더 불러도 손님이 빠져나가는 속도는 그대로입니다.
많은 사람이 "동시에 처리한다"와 "동시에 계산한다"를 같은 말로 씁니다. 하지만 이 둘은 완전히 다르고, 구분하지 못하면 스레드만 잔뜩 늘려놓고 왜 안 빨라지는지 모르는 상황에 빠집니다. 아래에서 작업 성격과 코어 수를 바꿔가며 직접 확인해 보세요.
코어가 하나여도 여러 일이 "진행 중"일 수 있습니다. 하나가 IO를 기다리는 동안 다른 하나가 CPU를 쓰면 되니까요. 이렇게 여러 작업이 시간을 나눠 겹쳐 진행되는 성질을 동시성이라고 합니다. 시간을 잘게 쪼개 번갈아 하는 것이므로, 어느 순간을 딱 잘라 보면 실제로 계산 중인 것은 하나뿐입니다.
코어가 4개라면 같은 순간에 4개의 계산이 물리적으로 함께 일어납니다. 이것이 병렬성입니다. 동시성은 코어 하나로도 가능하지만, 병렬성은 코어 수가 늘어야만 가능합니다.
작업은 크게 둘로 나뉩니다. 계산이 대부분인 CPU-bound 작업(이미지 변환, 암호화, 압축)과, 기다림이 대부분인 IO-bound 작업(디비 조회, 외부 API 호출, 파일 읽기)입니다. CPU-bound 작업은 코어 하나에서 스레드를 늘려봐야 갈아타기 비용만 늘고 총 시간은 그대로입니다 — 계산할 CPU가 하나뿐이니까요. 반대로 IO-bound 작업은 코어 하나에서도 스레드 수만큼 겹칩니다 — 기다림은 CPU를 쓰지 않으니까요.
아래 두 패널은 같은 작업을 스레드 1개와 4개로 처리합니다. 작업 성격을 CPU 작업으로 두고 CPU 수를 1로 하면, 스레드를 4배로 늘려도 완료 개수가 오히려 뒤처집니다(갈아타기 비용 때문). CPU 수를 4로 바꾸면 그제야 4배로 빨라집니다. 이번엔 작업 성격을 IO 작업으로 바꿔 보세요 — CPU가 하나여도 스레드 4개 쪽이 훨씬 빨리 끝납니다.
같은 순간에 실제로 계산되는 것은 코어 수를 넘지 못합니다. 코어가 네 개면 스레드를 백 개 만들어도 동시에 계산되는 것은 최대 네 개입니다. 그래서 CPU-bound 작업에서 스레드를 코어 수보다 훨씬 많이 만들면 얻는 것은 없고 컨텍스트 스위치 비용과 메모리만 늘어납니다. 반대로 IO-bound 작업에서는 스레드가 코어 수보다 훨씬 많아도 됩니다 — 대부분이 자고 있기 때문입니다.
출발점은 계산입니다. 요청 하나가 CPU를 c만큼, 기다림을 w만큼 쓴다면, 코어 하나를 놀리지 않으려면 대략 (c+w)/c 개의 동시 요청이 필요합니다. CPU 10밀리초에 대기 90밀리초라면 코어당 열 개입니다. CPU-bound는 코어 수 근처, IO-bound는 이 비율만큼 크게 — 이것이 스레드풀 크기와 커넥션 풀 크기를 정하는 첫 계산입니다.
시뮬레이터에서 해보기
이 단원의 용어
갈아타기는 기다림을 숨길 뿐, 계산을 늘리지는 못한다.
남은 문제
서버 요청은 어느 쪽일까요? 파싱 조금, 디비 대기 한참, 응답 조금 — 전형적인 IO 작업입니다 →