멀티태스킹
고친 사람 github-actions[bot]
멀티태스킹은 컴퓨터 한 대가 여러 프로그램을 한꺼번에 돌리게 해 줍니다. 운영체제가 프로그램마다 아주 짧은 차례를 나눠 주고 번갈아 돌립니다. 바꾸는 속도가 빨라서 사람 눈에는 모두가 함께 도는 것처럼 보입니다.
쉽고 빠른 이해
멀티태스킹은 프로그램 여러 개가 컴퓨터 한 대를 나눠 쓰게 하는 방식입니다. 음악을 틀어 둔 채 편집기에서 코드를 짜는 것이 그 예입니다.
이게 없으면 프로그램은 한 번에 하나씩만 돕니다. 앞 프로그램이 끝날 때까지 다음 프로그램은 기다립니다. 앞 프로그램이 디스크를 기다리는 동안에는 컴퓨터가 아무 일도 안 하고 놉니다.
도는 방식은 이렇습니다.
- 운영체제가 한 프로그램에 짧은 시간을 줍니다
- 시간이 다 되거나 그 프로그램이 무언가를 기다리게 되면, 하던 상태를 저장해 둡니다
- 다음 프로그램의 상태를 되살려 이어서 돌립니다
대가는 둘입니다. 프로그램을 바꿔 끼우는 데 시간이 듭니다. 그리고 언제 끊길지 모르니, 여러 프로그램이 같은 데이터를 고치면 서로의 수정을 덮어쓸 수 있습니다.
그래서 멀티태스킹은 디스크나 네트워크를 기다리는 일이 많을 때 이득이 큽니다. 계산만 하는 일은 오히려 바꾸는 시간만큼 늦게 끝납니다.
상세
바둑 고수 한 명이 상대 열 명과 동시에 둡니다. 고수는 판 사이를 돌며 한 판에 한 수씩 두고 다음 판으로 넘어갑니다. 상대 열 명은 저마다 자기 판이 끊김 없이 이어지고 있다고 느낍니다.
멀티태스킹은 운영체제가 CPU(Central Processing Unit, 중앙 처리 장치)를 여러 작업에 번갈아 내주는 방식입니다. 앞의 비유에서 고수는 CPU 입니다. 판 하나하나는 작업입니다.
CPU 는 명령을 하나씩 읽어 계산하는 장치입니다. CPU 하나는 한 순간에 작업 하나만 돌립니다. 그래서 여럿을 돌리려면 시간을 쪼개 나눠 써야 합니다.
작업은 영어로 태스크(task)입니다. 멀티태스킹이라는 이름이 여기서 왔습니다. 운영체제에서 작업은 실행 중인 프로그램, 곧 프로세스를 말합니다. 브라우저로 영상을 틀어 두고 편집기에서 코드를 짜면 두 프로그램이 각자의 프로세스로 CPU 를 나눠 씁니다.
한 프로세스 안에서 따로 도는 실행 흐름도 작업이 됩니다. 이 흐름을 스레드라고 부릅니다. 운영체제는 스레드도 프로세스와 같은 방법으로 번갈아 돌립니다.
멀티태스킹이 없으면 작업은 하나씩 차례로 끝까지 돕니다. 앞 작업이 디스크나 네트워크의 응답을 기다리는 동안에도 CPU 는 다른 일을 못 하고 놉니다. 키보드 앞의 사람도 앞 작업이 끝날 때까지 아무 답을 못 받습니다.
그래서 멀티태스킹은 두 가지를 노립니다. 하나는 CPU 가 노는 시간을 줄여, 같은 시간에 끝내는 일의 양을 늘리는 것입니다. 이 양이 처리량입니다.
다른 하나는 사람이나 프로그램이 답을 받기까지 걸리는 시간을 줄이는 것입니다. 이 시간이 응답 시간입니다.
작업을 번갈아 돌리는 순서
이 소절은 운영체제가 작업 A 에서 작업 B 로 넘어가는 한 번을 따라갑니다. 등장하는 장치는 넷입니다. 시간을 나누는 단위, 시간이 다 됐다고 알리는 신호, 다음 작업을 고르는 부분, 작업을 갈아 끼우는 일입니다.
운영체제는 작업에 한 번에 쓸 수 있는 짧은 시간을 줍니다. 이 시간을 타임 슬라이스라고 부릅니다. 사람이 알아채지 못할 만큼 짧게 잡아서, 여러 작업이 함께 도는 것처럼 보이게 합니다.
그런데 돌고 있는 작업은 CPU 를 스스로 내놓지 않을 수 있습니다. 그래서 운영체제는 하드웨어 타이머를 걸어 둡니다. 타이머는 정해진 시간이 지나면 CPU 에 신호를 보냅니다.
CPU 는 이 신호를 받으면 하던 일을 멈추고 운영체제의 코드로 넘어갑니다. 하던 일을 끊고 끼어드는 이런 신호를 인터럽트라고 합니다. 인터럽트 덕분에 운영체제는 작업이 내놓지 않아도 CPU 를 되찾습니다.
CPU 를 되찾은 운영체제는 다음에 돌릴 작업을 고릅니다. 이 일을 맡은 운영체제의 부분을 스케줄러라고 합니다. 고르는 규칙은 여럿입니다. 돌아가며 공평하게 주는 규칙도, 급한 작업부터 주는 규칙도 있습니다.
고른 다음에는 작업을 갈아 끼웁니다. 멈춘 작업이 어디까지 했는지, 계산 중이던 값이 무엇이었는지를 저장합니다. 그리고 다음 작업이 저장해 뒀던 것을 되살립니다. 이 저장과 복원을 문맥 교환이라고 합니다.
되살아난 작업은 자기가 멈춰 있었다는 것을 모릅니다. 하던 줄부터 이어서 돕니다. 아래 그림은 이 넷이 한 번 맞물리는 순서입니다.
sequenceDiagram
participant 타이머
participant A as 작업 A
participant 운영체제
participant B as 작업 B
Note over A: 타임 슬라이스 동안 돈다
타이머->>운영체제: 시간이 다 됐다는 인터럽트
운영체제->>A: 멈춘다
Note over 운영체제: A 의 상태를 저장한다
Note over 운영체제: 스케줄러가 B 를 고른다
운영체제->>B: 저장해 둔 상태를 되살린다
Note over B: 다음 타임 슬라이스 동안 돈다
시간이 다 되기 전에 넘어가기도 합니다. 작업이 디스크를 읽거나 네트워크 응답을 기다리게 되면 그동안 그 작업은 할 일이 없습니다. 이렇게 응답이 올 때까지 멈춰 서는 것을 블로킹이라고 합니다. 운영체제는 블로킹된 작업을 옆으로 비켜 두고 바로 다음 작업에 CPU 를 줍니다.
협력형과 선점형
작업에서 CPU 를 거둬들이는 방법은 둘입니다. 이 소절은 「넘길 때를 누가 정하나」로 둘을 가릅니다.
첫째는 작업이 스스로 내놓는 방식입니다. 작업은 할 일을 조금 하고 나서 차례를 넘기는 호출을 합니다. 이 호출을 양보(yield)라고 부릅니다. 양보에 기대는 이 방식이 협력형 멀티태스킹입니다.
협력형의 약점은 작업 하나가 양보를 안 하면 전부가 멈춘다는 것입니다. 무한 루프에 빠진 프로그램 하나가 컴퓨터 전체를 얼어붙게 만듭니다. 초기 개인용 컴퓨터의 운영체제 가운데 일부가 이 방식을 썼습니다.
둘째는 운영체제가 앞에서 본 타이머 인터럽트로 시간이 다 된 작업을 강제로 멈추는 방식입니다. 이 방식이 선점형 멀티태스킹입니다. 빼앗는 일 자체는 선점이라고 부릅니다. 오늘날 범용 운영체제는 이 방식으로 프로세스와 스레드를 돌립니다.
두 방식을 한 표로 견주면 이렇습니다.
| 협력형 | 선점형 | |
|---|---|---|
| 넘길 때를 정하는 쪽 | 작업 자신 | 운영체제 |
| 넘기는 계기 | 작업의 양보 | 타이머 인터럽트 · 블로킹 |
| 작업 하나가 안 넘기면 | 전부 멈춘다 | 시간이 다 되면 끊긴다 |
| 작업이 끊기는 시점 | 양보한 줄에서만 | 어느 줄에서나 |
표의 마지막 줄이 둘을 가릅니다. 협력형에서는 끊기는 시점을 코드가 압니다. 선점형에서는 아무 줄에서나 끊길 수 있어서, 뒤의 「멀티태스킹의 대가」에서 보는 문제가 생깁니다.
협력형은 운영체제에서는 밀려났지만 프로그램 안에서 다시 쓰입니다. 그 첫 도구가 코루틴입니다. 코루틴은 실행을 멈췄다가 그 지점부터 이어서 돌릴 수 있는 함수입니다. 기다릴 일이 생기면 스스로 멈춰 차례를 넘깁니다.
이벤트 루프는 준비된 일을 하나씩 꺼내 돌리는 반복문입니다. 루프가 꺼내 돌리는 일은 흔히 코루틴입니다. 코루틴이 양보하면 실행은 루프로 돌아옵니다. 이렇게 한 스레드 안에서 여러 작업이 번갈아 돕니다.
동시성과 병렬성
멀티태스킹은 여러 작업이 함께 도는 것처럼 보이게 합니다. 이 소절은 「함께 도는 것처럼 보이는 것」과 「같은 순간에 함께 도는 것」을 CPU 개수로 가릅니다.
CPU 하나로 하는 멀티태스킹에서 작업들은 한 순간에 하나씩만 돕니다. 아주 빨리 번갈아 돌 뿐입니다. 이렇게 여러 작업이 같은 기간 안에 함께 진행되는 성질을 동시성이라고 합니다.
요즘 CPU 는 안에 독립된 계산 단위를 여럿 둡니다. 이 단위를 코어라고 부릅니다. 코어 하나가 앞에서 말한 「CPU 하나」 몫을 합니다.
코어가 넷이면 작업 넷이 같은 순간에 함께 돕니다. 이처럼 여러 작업이 실제로 같은 순간에 도는 성질이 병렬성입니다. 동시성은 번갈아 돌기만 해도 성립합니다. 병렬성은 코어가 여럿 있어야 성립합니다.
아래 그림은 같은 두 작업이 코어 수에 따라 어떻게 놓이는지 보입니다. 위에서 아래로 시간이 흐릅니다.
flowchart TD
subgraph one["코어 하나 · 동시성"]
a1["작업 A"] --> b1["작업 B"] --> a2["작업 A"] --> b2["작업 B"]
end
subgraph two["코어 둘 · 병렬성"]
subgraph c1["코어 1"]
x1["작업 A"] --> x2["작업 A"]
end
subgraph c2["코어 2"]
y1["작업 B"] --> y2["작업 B"]
end
end
코어가 여럿이어도 멀티태스킹은 여전히 필요합니다. 돌릴 작업이 코어보다 훨씬 많기 때문입니다. 운영체제는 작업들을 코어마다 나눠 줍니다. 그리고 코어 하나하나에서 다시 번갈아 돌립니다.
다중 프로그래밍과 시분할
멀티태스킹과 비슷한 이름이 둘 더 있습니다. 둘은 CPU 를 넘기는 계기가 다릅니다.
다중 프로그래밍은 작업이 기다리게 될 때 CPU 를 다른 작업에 넘기는 방식입니다. CPU 가 노는 시간을 줄이는 것이 목적입니다. 계산만 오래 하는 작업이 있으면 그동안 다른 작업은 차례를 못 받습니다.
시분할은 정해진 시간이 지날 때마다 넘기는 방식입니다. 여러 사람이 단말기 앞에 앉아 한 컴퓨터를 쓰면서 각자 바로 답을 받게 하려고 나왔습니다.
오늘날 멀티태스킹은 두 계기를 함께 씁니다. 작업이 블로킹되면 넘기고, 타임 슬라이스가 다 돼도 넘깁니다. 앞의 「작업을 번갈아 돌리는 순서」에서 본 두 계기가 각각 이 둘에서 왔습니다.
멀티태스킹의 대가
번갈아 돌리기는 공짜가 아닙니다. 이 소절은 시간에서 드는 비용과 데이터에서 드는 비용을 차례로 봅니다.
문맥 교환을 하는 동안 CPU 는 어느 작업의 일도 하지 않습니다. 작업을 자주 바꿀수록 이 시간이 쌓입니다. 바꾸는 횟수가 너무 많으면 일하는 시간보다 바꾸는 시간이 커집니다.
숨은 비용도 있습니다. CPU 는 자주 쓰는 데이터를 가까이 두는 작은 저장소, 곧 CPU 캐시를 씁니다. 작업을 바꾼 직후의 캐시에는 앞 작업의 데이터가 들어 있습니다. 새 작업은 필요한 데이터를 메모리에서 다시 가져오느라 한동안 느리게 돕니다.
그래서 타임 슬라이스의 길이는 줄다리기입니다. 짧게 잡으면 작업마다 차례가 자주 돌아와 응답이 빠릅니다. 대신 바꾸는 비용이 늘어납니다. 길게 잡으면 비용은 줄지만 다른 작업이 오래 기다립니다.
데이터 쪽 비용은 선점형에서 나옵니다. 작업이 어느 줄에서나 끊길 수 있기 때문입니다.
두 작업이 변수 count 를 하나씩 늘린다고 합시다. 늘리기는 한 번에 끝나지 않습니다. 값 읽기, 1 더하기, 써넣기의 세 단계로 나뉩니다.
count 가 0 일 때 작업 A 가 0 을 읽은 직후에 끊겼다고 합시다. 그 사이 작업 B 가 0 을 읽어 1 을 써넣습니다. 다시 돌아온 A 는 아까 읽은 0 에 1 을 더해 1 을 써넣습니다.
두 번 늘렸어도 결과는 1 입니다. B 의 수정을 A 가 덮어썼기 때문입니다. 이처럼 결과가 실행 순서에 따라 달라지는 상황을 경쟁 상태라고 합니다.
막으려면 같은 데이터를 고치는 코드에 한 번에 한 작업만 들어가게 해야 합니다. 그 문을 지키는 장치가 락입니다. 한 작업이 락을 쥐고 있는 동안 다른 작업은 들어가지 못하고 기다립니다.
백엔드 서버에서 보이는 멀티태스킹
백엔드 서버도 멀티태스킹 위에서 돕니다. 이 소절은 요청을 받는 두 방식을 견주어, 언제 무엇이 맞는지를 봅니다.
흔한 방식은 요청 하나에 스레드 하나를 붙이는 것입니다. 스레드가 데이터베이스 응답을 기다리며 블로킹되면, 운영체제가 CPU 를 다른 요청의 스레드에 줍니다. 코드는 위에서 아래로 차례대로 읽힙니다. 번갈아 돌리기는 운영체제가 알아서 합니다.
이 방식은 연결이 아주 많아지면 버겁습니다. 동시에 붙은 연결이 수만 개면 스레드도 수만 개가 됩니다. 스레드마다 메모리를 차지합니다. 문맥 교환도 그만큼 잦아집니다.
그럴 때는 협력형을 프로그램 안으로 가져옵니다. 적은 수의 스레드가 이벤트 루프를 돌립니다. 요청마다의 작업은 기다릴 일이 생기면 스스로 양보합니다.
양보한 작업은 데이터베이스 응답을 기다리는 동안 스레드를 붙잡지 않습니다. 스레드는 루프로 돌아가 다른 요청의 작업을 꺼내 돌립니다. 응답이 오면 루프가 기다리던 작업을 「준비된 일」로 다시 꺼내 멈춘 곳부터 잇습니다.
운영체제를 거치지 않고 프로그램 안에서 바꾸니 바꾸는 비용이 작습니다. 이렇게 짜는 방식을 비동기 프로그래밍이라고 부릅니다.
대신 협력형의 약점도 같이 따라옵니다. 요청 하나가 오래 걸리는 계산을 붙들고 양보하지 않으면, 같은 루프에 있는 다른 요청이 전부 멈춥니다. 그래서 계산이 많은 일은 별도 스레드나 프로세스로 빼서 운영체제의 선점에 맡깁니다.
멀티태스킹이 일을 빨리 끝내 주는 것도 아닙니다. 계산만 하는 작업 둘을 코어 하나에서 번갈아 돌리면, 하나씩 돌린 것보다 바꾸는 비용만큼 늦게 끝납니다. 멀티태스킹은 기다림이 많은 일에서 이득이 큽니다. 계산을 빨리 끝내려면 코어 여럿을 함께 쓰는 병렬성이 필요합니다.
관련 항목
멀티태스킹이 속하는 상위 분류
멀티태스킹의 하위 종류
협력형 멀티태스킹 · 선점형 멀티태스킹 · 시분할 · 다중 프로그래밍
멀티태스킹이 번갈아 돌리는 실행 단위
프로세스 · 스레드 · 코루틴 · 그린 스레드 · 고루틴
작업을 갈아 끼울 때 거치는 장치와 단계
타이머 · 인터럽트 · 타이머 인터럽트 · 스케줄러 · 스케줄링 · 타임 슬라이스 · 선점 · 문맥 교환
다음 작업을 고르는 스케줄링 알고리즘
라운드 로빈 · 우선순위 스케줄링 · 다단계 피드백 큐 · 선입선출 스케줄링
멀티태스킹과 맞세워지는 실행 방식
병렬성 · 다중 처리 · 멀티코어 · 배치 처리 · 단일 태스킹
멀티태스킹에서 터지는 문제
경쟁 상태 · 데드락 · 기아 · 우선순위 역전 · 스래싱 · 문맥 교환 비용
공유 데이터를 지키는 동기화 도구
멀티태스킹을 프로그램 안으로 가져온 기법
이벤트 루프 · 비동기 · 논블로킹 입출력 · 블로킹 · CPU 캐시
멀티태스킹을 재는 지표
다른 이름: multitasking · 다중 작업 · 다중 태스킹