사전 다중 프로그래밍
개념

다중 프로그래밍

gabury1고친 사람 github-actions[bot]

다중 프로그래밍은 한 프로그램이 무언가를 기다리는 동안 컴퓨터의 계산 장치를 다른 프로그램에 넘겨 줍니다. 디스크나 네트워크를 기다리느라 계산 장치가 노는 시간을 줄이려고 나온 운영 방식입니다. 오늘날 컴퓨터가 프로그램 여러 개를 한꺼번에 돌리는 것도 이 생각 위에 서 있습니다.

쉽고 빠른 이해

다중 프로그래밍은 계산 장치가 쉬는 틈에 다른 프로그램을 돌립니다. 한 프로그램이 디스크에서 파일을 읽는 동안 계산 장치는 다른 프로그램의 계산을 합니다.

이렇게 하지 않으면 계산 장치는 디스크와 네트워크를 기다리며 멈춰 있습니다. 둘은 계산 장치에 비해 아주 느립니다. 프로그램을 하나씩만 돌리면 기계에서 가장 비싼 부품이 가장 오래 놀게 됩니다.

  1. 여러 프로그램을 메모리에 함께 올려 둡니다
  2. 돌던 프로그램이 디스크나 네트워크를 기다리게 되면 운영체제가 계산 장치를 다른 프로그램에 넘깁니다
  3. 기다리던 일이 끝나면 그 프로그램은 다시 계산 장치 차례를 기다립니다

대가도 있습니다. 메모리를 여러 프로그램이 나눠 써야 합니다. 서로 침범하지 않게 막는 일도 필요합니다. 계산 장치를 넘길 때마다 하던 일을 저장하고 되돌리는 비용도 듭니다.

상세

요리사 한 명이 냄비 셋을 불에 올려 두었다고 해 봅시다. 한 냄비가 끓기를 기다리는 동안 요리사는 다른 요리의 채소를 썹니다. 냄비가 끓으면 다시 돌아가 다음 일을 합니다. 요리사는 한 사람이지만 손이 쉬는 시간이 거의 없습니다.

컴퓨터에서 요리사는 계산 장치, 곧 CPU(Central Processing Unit, 중앙 처리 장치)입니다. CPU 는 한 순간에 명령 하나의 흐름만 실행합니다. 다중 프로그래밍은 이 CPU 하나를 여러 프로그램이 번갈아 쓰게 하는 운영체제의 운영 방식입니다. 프로그램 여러 개를 메모리에 함께 올려 둡니다. 지금 도는 프로그램이 멈춰 기다리면 다른 프로그램으로 바꿔 돌립니다.

메모리에 올라와 실행 중인 프로그램 하나하나를 프로세스라고 부릅니다. 같은 프로그램을 두 번 띄우면 프로세스가 둘 생깁니다. 아래에서는 운영체제 안쪽을 볼 때만 프로세스라는 말을 씁니다.

프로그램이 멈춰 기다리는 순간

프로그램은 계산만 하지 않습니다. 파일을 읽고, 데이터베이스에 질의를 보내고, 네트워크로 응답을 받습니다. 이렇게 CPU 바깥의 장치와 데이터를 주고받는 일을 입출력(I/O, Input/Output)이라고 합니다.

입출력은 CPU 의 계산보다 훨씬 느립니다. 디스크는 데이터를 찾아 읽는 데 시간이 걸립니다. 네트워크는 상대가 답할 때까지 기다려야 합니다. 그동안 프로그램은 다음 줄로 넘어갈 수 없습니다. 읽은 데이터가 있어야 그다음 계산을 하기 때문입니다.

프로그램을 하나만 메모리에 두면 이 기다림이 그대로 CPU 의 빈 시간이 됩니다. 옛 일괄 처리 시스템이 그랬습니다. 일괄 처리는 사람이 모아 둔 작업을 컴퓨터가 차례로 하나씩 끝내는 방식입니다.

작업 하나가 테이프나 디스크를 읽는 동안 CPU 는 할 일 없이 멈춰 있었습니다. 당시 CPU 는 기계에서 가장 비싼 부품이었습니다. 그래서 CPU 가 노는 시간은 곧 돈을 버리는 시간이었습니다.

두 프로그램으로 견주어 보기

같은 일을 하나씩 돌릴 때와 함께 돌릴 때를 견주어 봅니다. 프로그램 A 와 B 가 있습니다. 둘 다 「1초 계산 → 1초 디스크 읽기 → 1초 계산」을 합니다. 숫자는 차이를 보이려고 고른 값입니다.

먼저 하나씩 돌립니다. A 가 다 끝나야 B 가 시작합니다.

시각 CPU 디스크
0~1초 A 계산 쉼
1~2초 쉼 A 읽기
2~3초 A 계산 쉼
3~4초 B 계산 쉼
4~5초 쉼 B 읽기
5~6초 B 계산 쉼

전체 6초가 걸렸습니다. CPU 는 그중 4초만 일했습니다. 두 번의 1초는 디스크를 기다리며 놀았습니다.

이번에는 둘을 메모리에 함께 올립니다. A 가 디스크를 기다리는 동안 B 가 CPU 를 씁니다.

시각 CPU 디스크
0~1초 A 계산 쉼
1~2초 B 계산 A 읽기
2~3초 A 계산 B 읽기
3~4초 B 계산 쉼

전체 4초가 걸렸습니다. CPU 는 4초 내내 일했습니다. A 는 두 경우 모두 3초에 끝납니다. B 는 6초에서 4초로 당겨졌습니다. 같은 시간에 끝내는 일의 양, 곧 처리량이 늘어난 것입니다.

CPU 를 넘기는 흐름

CPU 를 언제 누구에게 넘길지는 운영체제가 정합니다. 대기 중인 프로그램 가운데 다음에 돌릴 것을 고르는 일을 스케줄링이라고 합니다.

넘기는 계기는 프로그램의 입출력 요청입니다. 프로그램은 파일을 직접 읽지 못하고 운영체제에 부탁합니다. 이 부탁을 시스템 콜이라고 합니다. 부탁을 받은 운영체제는 디스크에 읽기를 시켜 둡니다. 그리고 결과를 기다리는 대신 다른 프로그램에 CPU 를 넘깁니다.

디스크는 일을 마치면 CPU 에 신호를 보냅니다. 이 신호를 인터럽트라고 합니다. 인터럽트를 받은 운영체제는 A 가 기다리던 데이터가 왔음을 알고 A 를 다시 「돌 수 있음」으로 바꿔 둡니다. 아래 그림이 이 흐름입니다.

sequenceDiagram
    participant A as 프로그램 A
    participant 운영체제
    participant B as 프로그램 B
    participant 디스크
    A->>운영체제: 파일을 읽어 달라 (시스템 콜)
    운영체제->>디스크: 읽기를 시킨다
    운영체제->>B: CPU 를 넘긴다
    Note over B: B 가 계산한다
    디스크->>운영체제: 다 읽었다 (인터럽트)
    Note over 운영체제: A 를 다시 돌 수 있음으로 바꾼다
    운영체제->>A: 차례가 오면 CPU 를 넘긴다

인터럽트가 없으면 운영체제는 디스크가 끝났는지 계속 물어봐야 합니다. 그렇게 묻는 동안에도 CPU 가 쓰입니다. 인터럽트 덕분에 CPU 는 묻지 않고 다른 일을 하다가 알림이 올 때만 돌아봅니다.

장치와 메모리 사이에서 데이터를 옮기는 일도 CPU 가 맡지 않습니다. DMA(Direct Memory Access, 직접 메모리 접근)는 장치가 CPU 를 거치지 않고 메모리에 데이터를 써 넣는 방식입니다. 디스크가 읽은 데이터를 바이트마다 CPU 가 옮겨야 한다면 CPU 는 결국 입출력에 붙잡힙니다.

프로세스가 오가는 세 상태

운영체제는 메모리에 있는 프로세스마다 지금 어떤 처지인지를 기록해 둡니다. 이 기록을 프로세스 상태라고 부릅니다. 흔히 세 가지로 나눕니다.

상태 뜻
실행 지금 CPU 를 쓰고 있다
준비 CPU 만 주면 바로 돌 수 있다
대기 입출력 같은 바깥 일이 끝나기를 기다린다

앞 그림에서 A 를 「돌 수 있음」으로 바꾼 일이 대기에서 준비로 옮긴 것입니다. CPU 가 하나면 실행 상태는 한 번에 하나뿐입니다. 나머지는 준비나 대기에 있습니다.

운영체제는 준비 상태의 프로세스를 줄 세워 두고 거기서 다음 차례를 고릅니다. 이 줄을 준비 큐라고 합니다.

stateDiagram-v2
    준비 --> 실행: 운영체제가 고른다
    실행 --> 대기: 입출력을 요청한다
    대기 --> 준비: 입출력이 끝난다 (인터럽트)
    실행 --> 준비: 시간이 다 돼 빼앗긴다

다중 프로그래밍의 핵심은 「실행 → 대기」 화살표입니다. 하나만 돌리는 시스템에서는 이 화살표가 CPU 의 빈 시간이 됩니다. 여러 개를 돌리면 그 순간 준비 큐에서 다른 프로세스를 꺼내 실행에 올립니다.

「실행 → 준비」 화살표는 기다리지 않는데도 CPU 를 빼앗길 때 생깁니다. 뒤의 시분할에서 정해진 시간이 다 되면 운영체제가 CPU 를 거둬 가는 경우입니다. 순수한 다중 프로그래밍에는 이 화살표가 없습니다.

CPU 를 넘길 때 드는 비용

CPU 를 다른 프로세스에 넘기려면 지금 프로세스가 어디까지 했는지를 저장해야 합니다. 나중에 돌아왔을 때 이어서 실행하기 위해서입니다. 저장할 것은 CPU 안의 작은 저장 칸인 레지스터의 값과 다음에 실행할 명령의 위치 같은 것들입니다.

이렇게 한 프로세스의 실행 상태를 저장하고 다른 프로세스의 상태를 불러오는 일을 문맥 교환이라고 합니다. 문맥 교환을 하는 동안 CPU 는 어느 프로그램의 일도 하지 않습니다. 그래서 전환이 너무 잦으면 아낀 시간보다 전환에 쓰는 시간이 커질 수 있습니다.

메모리를 나눠 쓰는 문제

프로그램 여럿을 메모리에 함께 두면 새 걱정이 생깁니다. 한 프로그램이 실수로 남의 메모리를 덮어쓰면 그 프로그램까지 망가집니다. 운영체제의 메모리를 건드리면 기계 전체가 멈춥니다.

이를 막으려고 다중 프로그래밍에는 메모리 보호가 따라붙습니다. 메모리 보호는 프로세스마다 쓸 수 있는 메모리 범위를 정해 둡니다. 그 밖을 건드리면 CPU 가 막습니다.

프로그램이 이 범위를 스스로 바꿀 수 있다면 보호는 소용이 없습니다. 그래서 CPU 는 실행 권한을 사용자 모드와 커널 모드 둘로 가릅니다. 범위를 바꾸는 명령은 커널 모드에서만 받아들입니다. 응용 프로그램은 권한이 적은 사용자 모드에서 돌기 때문에 제 범위를 넓힐 수 없습니다.

메모리 크기도 문제입니다. 함께 올릴 프로그램이 늘면 메모리가 모자랍니다. 그때 당장 안 쓰는 프로세스를 디스크로 내려 두었다가 다시 올리는 방법이 스와핑입니다.

가상 메모리도 이 문제를 풀려고 발전했습니다. 가상 메모리는 프로그램 전체가 아니라 지금 필요한 부분만 메모리에 둡니다. 또 프로그램마다 따로 주소 공간을 보게 합니다. 주소 공간은 한 프로그램이 쓸 수 있는 메모리 주소의 범위입니다.

메모리에 몇 개를 올리나

메모리에 함께 올라와 있는 프로세스의 수를 다중 프로그래밍 정도라고 부릅니다. 이 수가 하나면 하나씩 돌리는 시스템과 같습니다. 수를 늘리면 누군가 기다리는 동안 돌릴 다른 프로세스가 있을 확률이 커집니다. 그래서 CPU 가 일하는 시간의 비율, 곧 CPU 이용률이 올라갑니다.

수를 끝없이 늘릴 수는 없습니다. 가상 메모리처럼 필요한 부분만 메모리에 두는 경우를 생각해 봅시다. 프로세스가 늘어 저마다 받는 메모리가 줄면 필요한 데이터가 메모리에 없는 일이 잦아집니다.

그러면 운영체제는 디스크에서 데이터를 가져오느라 바빠집니다. CPU 는 다시 놀게 됩니다. 이 상태를 스래싱이라고 합니다.

이용률은 어느 수까지는 오르다가 그 뒤로는 오히려 떨어집니다. 운영체제가 메모리에 함께 올릴 프로세스 수를 조절하는 까닭입니다.

프로세스의 성격도 결과를 가릅니다. 계산이 대부분인 프로세스를 CPU 바운드, 기다림이 대부분인 프로세스를 입출력 바운드라고 부릅니다. 기다림이 많은 프로세스끼리만 모여 있으면 모두가 동시에 기다리는 순간이 생깁니다. 계산이 많은 것과 기다림이 많은 것이 섞여 있어야 CPU 와 장치가 함께 바쁩니다.

시분할 · 멀티태스킹 · 다중 처리와 견주기

다중 프로그래밍과 이름이 비슷한 말이 셋 있습니다. 가르는 기준은 「CPU 를 언제 넘기나」와 「CPU 가 몇 개인가」입니다.

이름 CPU 를 넘기는 때 CPU 수 무엇을 늘리려 하나
다중 프로그래밍 돌던 프로그램이 기다릴 때 하나여도 된다 CPU 이용률 · 처리량
시분할 정해진 짧은 시간이 지날 때마다 하나여도 된다 사람이 받는 응답 속도
멀티태스킹 위 둘을 함께 하나여도 된다 여러 일을 동시에 하는 것처럼 보이기
다중 처리 해당 없음 여럿 같은 시간에 실제로 함께 도는 일의 수

순수한 다중 프로그래밍은 프로그램이 스스로 기다리게 될 때만 CPU 를 넘깁니다. 그래서 계산만 오래 하는 프로그램이 있으면 그동안 다른 프로그램은 차례를 못 받습니다. 기다림이 없으면 CPU 를 내놓을 계기도 없기 때문입니다.

시분할은 이 빈틈을 메웁니다. 운영체제는 정해진 시간이 지나면 CPU 에 인터럽트를 거는 타이머를 걸어 둡니다. 시간이 다 되면 돌던 프로그램을 멈춥니다. 그리고 CPU 를 빼앗아 다음 프로그램에 줍니다.

이렇게 운영체제가 강제로 CPU 를 넘기는 것을 선점이라고 합니다. 앞 상태 그림의 「실행 → 준비」 화살표가 바로 이것입니다. 여러 사람이 터미널 앞에 앉아 한 컴퓨터를 쓸 때 각자가 기다림 없이 답을 받으려면 이 방식이 필요했습니다.

멀티태스킹은 오늘날 운영체제가 기다릴 때도 넘기고 시간이 다 돼도 넘기는 방식을 두루 부르는 말입니다. 앞 표의 다중 프로그래밍과 시분할을 함께 쓰는 셈입니다.

다중 처리는 축이 다릅니다. CPU 를 여러 개 두어 프로그램 여럿이 같은 순간에 정말로 함께 돕니다. 그렇다고 다중 프로그래밍이 사라지지는 않습니다. CPU 하나하나는 여전히 여러 프로그램을 번갈아 돌립니다.

이 구분은 동시성과 병렬성의 구분과 같은 선을 따릅니다. 번갈아 돌아서 함께 진행되는 것처럼 보이는 것이 동시성입니다. 같은 순간에 실제로 함께 도는 것이 병렬성입니다.

백엔드 서버에서 보이는 같은 생각

백엔드 서버가 요청을 처리하는 시간도 대부분 기다림입니다. 데이터베이스 응답, 다른 서비스의 응답, 디스크 읽기를 기다립니다.

스레드는 한 프로세스 안에서 따로 실행되는 흐름입니다. 운영체제는 스레드도 프로세스처럼 번갈아 돌립니다. 요청 처리 스레드 하나가 데이터베이스 응답을 기다리는 동안 운영체제는 CPU 를 다른 스레드에 줍니다.

같은 생각을 프로그램 안에서 직접 하는 방식도 있습니다. 비동기 입출력은 응답을 기다리는 동안 멈추지 않고 다음 요청을 먼저 처리합니다. 응답이 오면 그때 하던 일로 돌아옵니다. 운영체제가 프로세스 사이에서 하던 바꿔 돌리기를 프로그램이 요청 사이에서 하는 셈입니다.

여럿이 하나를 나눠 쓴다는 문제는 데이터베이스에서도 같은 모양으로 나타납니다. 여러 사용자의 작업이 같은 레코드를 번갈아 읽고 고칩니다. 여러 프로그램이 메모리와 장치를 나눠 쓰듯 여러 작업이 데이터를 나눠 씁니다. 서로의 작업이 엉키지 않게 막으려면 락 같은 장치가 필요합니다.

관련 항목

다중 프로그래밍이 속하는 상위 분류

운영체제 · 커널 · 동시성 · 자원 관리

다중 프로그래밍을 받쳐 주는 하드웨어 장치

CPU · 인터럽트 · DMA · 타이머 · 레지스터 · 메모리 보호 · 사용자 모드와 커널 모드

CPU 를 넘기는 데 쓰는 운영체제 기법

스케줄링 · 문맥 교환 · 시스템 콜 · 선점 · 비선점 스케줄링 · 준비 큐 · 프로세스 상태

함께 올린 프로그램이 메모리를 나눠 쓰는 기법

가상 메모리 · 스와핑 · 메모리 분할 · 페이징 · 주소 공간

다중 프로그래밍과 견주는 실행 방식

배치 처리 · 시분할 · 멀티태스킹 · 다중 처리 · 병렬성

다중 프로그래밍이 늘리려는 지표

CPU 이용률 · 처리량 · 응답 시간 · 반환 시간 · 다중 프로그래밍 정도

프로세스를 성격으로 가르는 이름

CPU 바운드 · 입출력 바운드 · 입출력 · 입출력 대기 · 디스크 입출력

함께 돌릴 때 터지는 문제

스래싱 · 데드락 · 기아 · 경쟁 상태 · 락 경합

번갈아 돌기를 프로그램 안으로 가져온 실행 단위와 기법

프로세스 · 스레드 · 스레드 풀 · 비동기 · 논블로킹 입출력 · 이벤트 루프 · 코루틴

여럿이 하나를 나눠 쓰는 문제가 다시 나타나는 분야

데이터베이스 · 공유 접근 · 트랜잭션 · 락 · 동시성 제어

다른 이름: multiprogramming · 멀티프로그래밍 · 다중 프로그램 처리