문서 읽는 데 58분 · B2

B-2: 스레드와 CPU 스케줄링

목차 29
전체 12강 중 4강 · CS 기초지식
난이도 · 중급

ℹ️컴퓨터 구조·운영체제·네트워크 — 언어 밑에 깔린 원리와 기술 면접 CS 질문 대비. 코딩에 어느 정도 익숙해진 뒤가 좋아요.

안녕하세요, 홍순구 튜터입니다. CS 기초 네 번째 시간이에요.

지난 시간(B-1) 마지막에 우리는 작은 약속을 하나 남겼습니다. 운영체제가 프로세스 여러 개를 번갈아 돌려 동시 실행처럼 보이게 한다는 걸 봤는데, 프로세스 하나를 통째로 갈아 끼우는 컨텍스트 스위칭이 생각보다 무거운 일이라고 했죠. 메모리 공간까지 통째로 바꿔야 하니까요. 그래서 사람들이 더 가벼운 실행 단위를 고안했고, 그 이름이 스레드(thread) — "하나의 프로세스 안에서 메모리를 공유하며 여러 갈래로 동시에 흐르는 실행의 흐름"이라고 살짝 꺼내고 끝냈습니다. 오늘 그 스레드를 정식으로 만납니다.

그리고 약속이 하나 더 있었어요. 코어가 하나일 때 "번갈아 빠르게" 도는 것과, 코어가 여럿일 때 "진짜 한꺼번에" 도는 것 — 겉보기엔 똑같지만 속이 다른 이 둘에 붙는 정확한 이름을 오늘 만난다고 했죠. 동시성과 병렬성입니다.

스레드와 스케줄링 영역은 운영체제 면접에서 가장 단골 중의 단골이에요. "프로세스와 스레드의 차이를 설명해보세요"·"컨텍스트 스위칭 비용이 왜 큰가요"·"동시성과 병렬성이 뭐가 다른가요"·"라운드 로빈이 뭔가요" — 이 넷은 거의 반드시 나옵니다. 오늘 다 잡고 갑니다.

오늘 우리가 걸을 길은 이렇습니다.

텍스트
   오늘의 여정 — 스레드, 그리고 CPU 스케줄링

   ① 스레드란 — 프로세스 안에서 메모리를 공유하는 가벼운 실행 흐름
   ② 프로세스 vs 스레드 — 무엇을 공유하고 무엇을 따로 갖나 (면접 최단골)
   ③ 멀티스레드의 두 얼굴 — 빨라지는 대가로 생기는 충돌
   ④ 컨텍스트 스위칭 — 왜 그렇게 비싼가 (프로세스 간 vs 스레드 간)
   ⑤ 동시성 vs 병렬성 — 번갈아 빠르게 vs 진짜 한꺼번에
   ⑥ CPU 스케줄링이란 — Ready 줄에서 누구에게 CPU를 줄까
   ⑦ 스케줄링 알고리즘 ① — FCFS·SJF·SRT (도착·길이로 줄 세우기)
   ⑧ 스케줄링 알고리즘 ② — 라운드 로빈·우선순위·MLFQ (시간·우선순위로)

①~⑤는 프로세스를 더 잘게 쪼갠 실행 단위인 스레드 이야기, ⑥~⑧은 그 실행 단위들에게 CPU를 어떤 순서로 나눠줄지 정하는 스케줄링 이야기예요. 길목마다 면접 단골 질문이 숨어 있습니다.

💡 오늘 수업의 핵심 — "스레드는 프로세스 안에서 메모리를 공유하는 가벼운 실행 단위이고, CPU 스케줄링은 Ready 줄에 선 실행 단위들에게 CPU를 나눠주는 순서 정하기다" 🎯

🎯 학습 목표

  • 스레드가 프로세스와 어떻게 다른지(자원 공유의 차이)를 구분하고, 면접 최단골인 "프로세스 vs 스레드"에 원리로 답하며, 멀티스레드의 장단점을 설명합니다.
  • 컨텍스트 스위칭이 왜 비용이 큰지, 프로세스 간 전환과 스레드 간 전환이 왜 다른지 이해하고, 동시성과 병렬성을 정확히 구분합니다.
  • CPU 스케줄링의 선점·비선점을 구분하고, FCFS·SJF·라운드 로빈·우선순위·MLFQ 알고리즘과 평가지표(대기·응답·반환시간·처리량)를 설명합니다.

Step 1: "스레드란 — 프로세스 안의 가벼운 실행 흐름"

지난 시간 마지막 장면을 떠올려봅시다. 운영체제가 프로세스 A를 잠깐 멈추고 B로 갈아 끼울 때, 메모리 공간까지 통째로 바꿔야 해서 컨텍스트 스위칭이 무겁다고 했어요. 그런데 생각해보면, 같은 프로그램 안에서 여러 일을 동시에 하고 싶을 때마다 매번 무거운 프로세스를 따로 만드는 건 낭비예요. 워드에서 글자를 입력받으면서, 동시에 자동 저장도 하고, 맞춤법 검사도 돌리고 싶은데, 이걸 다 별개의 프로세스로 만들면 메모리도 많이 들고 서로 데이터 주고받기도 번거롭죠(프로세스는 IPC를 거쳐야 하니까요).

그래서 나온 발상이 이겁니다. 하나의 프로세스 안에 실행의 흐름을 여러 갈래로 두자. 이 갈래 하나하나가 스레드(thread)예요. thread는 영어로 "실, 가닥"이라는 뜻인데, 한 프로세스라는 천 안에 여러 가닥의 실이 동시에 흐른다고 생각하면 됩니다.

스레드를 한 줄로 정의하면, 프로세스 안에서 실제로 CPU를 받아 명령을 실행하는 흐름의 단위입니다. 사실 우리가 지금까지 "프로세스가 실행된다"고 말한 것도, 정확히는 그 프로세스 안의 스레드가 실행되는 거예요. 모든 프로세스는 적어도 하나의 스레드(메인 스레드)를 갖고 시작하고, 필요하면 그 안에 스레드를 여러 개 둘 수 있습니다. 이게 멀티스레드예요.

핵심은 무엇을 같이 쓰고 무엇을 따로 갖느냐입니다. 한 프로세스 안의 스레드들은 코드·데이터·힙을 공유하고, 각자 자기만의 스택과 레지스터(프로그램 카운터 포함)는 따로 가집니다.

텍스트
   ┌────────────────────────────────┐
   │  Process                       │
   │                                │
   │  Thread 1      Thread 2        │──── 한 프로세스 안에 여러 스레드
   │  [ Stack ]     [ Stack ]       │──── 스택은 스레드마다 따로
   │  [ Regs/PC ]   [ Regs/PC ]     │──── 레지스터·PC도 스레드마다 따로
   │                                │
   │  Code | Data | Heap            │──── 코드·데이터·힙은 모두 공유
   └────────────────────────────────┘

왜 코드·데이터·힙은 공유하고 스택·레지스터만 따로일까요? 스레드들은 같은 프로그램의 일부라 실행할 코드와 데이터를 같이 봐도 됩니다. 같은 힙을 공유하니 한 스레드가 만든 객체를 다른 스레드가 바로 쓸 수 있어 데이터 교환이 빠르죠. 하지만 각 스레드는 자기만의 함수 호출 흐름을 가져야 해요. 지금 어떤 함수 안에 있고 지역 변수가 뭔지(스택), 어디까지 실행했는지(PC·레지스터)는 스레드마다 다르니까요. 그래서 이건 따로 둡니다.

비유하면 한 사무실에서 일하는 여러 직원이에요. 사무실(프로세스)의 공용 문서함과 화이트보드(코드·데이터·힙)는 다 같이 보고 쓰지만, 각자 자기 책상과 메모장(스택·레지스터)은 따로 갖죠. 직원이 여럿이라 일이 동시에 여러 갈래로 진행되지만, 같은 사무실 자원을 공유하니 협업은 빠릅니다.

🙋 학생 질문 — "프로세스도 스레드가 있다면서요? 그럼 프로세스랑 스레드랑 같은 거 아니에요?"

헷갈리기 딱 좋은 지점이에요. 둘은 역할이 다릅니다.

프로세스는 "자원을 담는 그릇"이에요. 메모리 공간, 열어둔 파일, 권한 같은 자원을 소유하죠. 스레드는 그 그릇 안에서 "실제로 CPU를 받아 도는 흐름"입니다. 비유하면 프로세스는 무대와 소품(자원)이고, 스레드는 그 무대 위에서 실제로 연기하는 배우예요.

무대가 있어도 배우가 없으면 아무 일도 안 일어나죠. 그래서 모든 프로세스는 최소 한 명의 배우(메인 스레드)를 데리고 시작합니다. 멀티스레드는 한 무대에 배우가 여럿인 거예요. 같은 소품(공유 메모리)을 쓰면서 각자 자기 대본 위치(스택·PC)를 따로 기억하는 거죠.

🎯 면접에서는 이렇게 답하세요

"스레드는 프로세스 안에서 실제로 CPU를 받아 실행되는 흐름의 단위입니다. 한 프로세스는 최소 하나의 스레드를 갖고, 여러 스레드를 둘 수 있어요. 같은 프로세스의 스레드들은 코드·데이터·힙을 공유하고, 각자 자신만의 스택과 레지스터(프로그램 카운터 포함)를 따로 가집니다. 코드와 데이터를 공유하니 스레드끼리 데이터를 주고받기 쉽고, 스택이 따로라 각 스레드가 독립된 함수 호출 흐름을 가질 수 있습니다. 한마디로 프로세스가 자원을 소유하는 단위라면, 스레드는 그 자원 위에서 실제로 실행되는 단위입니다."

💡 스레드가 무엇을 공유하고 무엇을 따로 갖는지 봤어요. 그럼 프로세스와 스레드를 나란히 놓고 정확히 비교하면 어떻게 될까요? 운영체제 면접에서 가장 자주 나오는 질문, "프로세스 vs 스레드"를 다음 Step에서 표로 정리합니다.


Step 2: "프로세스 vs 스레드 — 무엇을 나눠 갖고 무엇을 따로 갖나"

이 비교는 운영체제 면접의 1순위 단골 질문이에요. 그냥 "스레드가 더 가볍다" 정도로 외우면 꼬리 질문에서 무너집니다. 무엇이 어떻게 다른지를 항목별로 잡아둡시다.

핵심 차이는 하나예요. 프로세스는 메모리를 통째로 따로 갖고(격리), 스레드는 한 프로세스 안에서 대부분의 메모리를 공유한다(코드·데이터·힙). 여기서 나머지 차이가 전부 따라 나옵니다.

구분 프로세스 스레드
메모리 각자 독립 (코드·데이터·힙·스택 모두 따로) 코드·데이터·힙 공유 / 스택·레지스터만 따로
통신 IPC 필요 (파이프·공유 메모리·메시지 패싱) 공유 메모리를 직접 읽고 씀 (빠름)
생성 비용 무겁다 (독립 메모리 공간을 통째로 마련) 가볍다 (스택·레지스터만 추가)
컨텍스트 스위칭 무겁다 (메모리 주소공간 교체) 가볍다 (주소공간 그대로)
한쪽이 죽으면 다른 프로세스는 안전 (격리) 프로세스 전체가 위험 (공유)

표를 한 줄로 요약하면, 프로세스는 자원을 소유하는 단위이고 스레드는 CPU 실행의 단위예요. 그래서 여러 일을 시킬 때 선택지가 둘로 갈립니다. 일들을 격리된 프로세스 여러 개로 돌리는 멀티프로세스, 아니면 한 프로세스 안 스레드 여러 개로 돌리는 멀티스레드죠.

지난 시간에 본 크롬이 대표적인 멀티프로세스예요. 탭마다 별도 프로세스로 격리해서, 한 탭이 죽어도 다른 탭은 멀쩡합니다(안정성을 산 거죠 — B-1 생각해볼 주제에서 다룬 그 트레이드오프예요). 반대로 워드 같은 한 프로그램이 입력·자동저장·맞춤법검사를 동시에 처리하는 건 멀티스레드에 가깝습니다. 긴밀하게 같은 문서 데이터를 공유하며 가볍게 협력해야 하니까요.

🙋 학생 질문 — "스레드는 가볍고 빠르고 다 좋아 보이는데, 왜 굳이 프로세스로 나눠요? 다 스레드로 하면 안 돼요?"

공유에는 양면이 있어요. 빠른 만큼 위험합니다.

스레드들은 같은 메모리(힙·데이터)를 공유하죠. 그래서 한 스레드가 잘못된 메모리에 접근하거나 치명적인 오류로 죽으면, 그 프로세스 전체가, 즉 그 안의 모든 스레드가 같이 죽습니다. 격리가 없으니까요. 반면 프로세스는 메모리가 분리돼 있어, 하나가 죽어도 다른 프로세스는 안전해요.

그래서 안정성·보안이 중요한 곳(브라우저 탭, 서로 신뢰할 수 없는 코드)은 무겁더라도 프로세스로 격리하고, 가볍고 긴밀한 협력이 필요한 곳은 스레드로 공유합니다. "공유하면 빠르지만 한쪽이 무너지면 같이 위험하다, 격리하면 안전하지만 무겁다" — 이 트레이드오프를 상황에 맞게 고르는 거예요.

🎯 면접에서는 이렇게 답하세요

"프로세스는 각자 독립된 메모리 공간을 가진 자원 소유의 단위이고, 스레드는 한 프로세스 안에서 코드·데이터·힙을 공유하며 실제로 실행되는 CPU 흐름의 단위입니다. 스레드는 스택과 레지스터만 따로 갖죠. 그래서 스레드는 생성·전환이 가볍고 데이터 공유가 쉽지만, 한 스레드의 오류가 프로세스 전체를 무너뜨릴 수 있습니다. 반면 프로세스는 격리돼 한쪽이 죽어도 안전하지만 생성·통신 비용이 큽니다. 안정성이 중요하면 프로세스로, 가볍고 긴밀한 협력이 필요하면 스레드로 나눕니다."

💡 표에서 "스레드는 컨텍스트 스위칭이 가볍다"고 적었는데, 도대체 컨텍스트 스위칭이 정확히 뭐고 왜 프로세스는 무겁고 스레드는 가벼운 걸까요? 그 전에, 멀티스레드를 쓰면 구체적으로 무엇을 얻고 무엇을 잃는지부터 정리하고 갑시다. 다음 Step.


Step 3: "멀티스레드의 두 얼굴 — 장점과 대가"

스레드를 여러 개 쓰면 뭐가 좋을까요? 그리고 공짜로 좋기만 할까요? 멀티스레드의 장점과 대가를 양손에 들고 봐야, 면접에서 "멀티스레드면 무조건 빠른가요?" 같은 꼬리 질문을 받아낼 수 있습니다.

먼저 장점 넷입니다.

  • 응답성(responsiveness) — 한 스레드가 오래 걸리는 일을 해도, 다른 스레드가 사용자 입력을 계속 받아줍니다. 파일을 다운로드하는 중에도 화면 스크롤이 되는 게 이 덕분이에요. 다운로드 스레드와 UI 스레드가 따로 도니까요.
  • 자원 공유(resource sharing) — 같은 힙·데이터를 공유하니, 스레드끼리 데이터를 주고받을 때 IPC 같은 별도 통로가 필요 없습니다. 그냥 같은 메모리를 읽고 쓰면 돼서 빠르죠.
  • 경제성(economy) — 스레드 생성과 전환은 프로세스보다 훨씬 가볍습니다. 메모리 공간을 통째로 새로 만들지 않고 스택·레지스터만 추가하면 되니까요.
  • 확장성(scalability) — 코어가 여러 개면, 한 프로세스의 스레드들을 여러 코어에 나눠 진짜 동시에 실행할 수 있습니다. (이 "진짜 동시"가 Step 5의 병렬성이에요.)

그런데 이 좋은 점들의 뿌리인 "공유"가 바로 대가의 뿌리이기도 합니다.

  • 공유 자원 충돌 — 여러 스레드가 같은 데이터를 동시에 건드리면 값이 꼬일 수 있어요. 이걸 경쟁 상태(race condition)라고 합니다. 순서를 맞추는 장치(동기화)가 없으면 결과가 틀어집니다.
  • 안정성 — 격리가 없으니, 한 스레드의 치명적 오류가 프로세스 전체를 같이 무너뜨립니다(Step 2에서 본 그 위험이에요).
  • 디버깅 난이도 — 스레드들의 실행 순서는 매번 달라질 수 있어서, 어쩌다 한 번 터지는 버그를 재현하기가 어렵습니다.

비유하면 공동 주방에서 일하는 요리사 여럿이에요. 재료와 도구(공유 메모리)를 같이 쓰니 빠르고 효율적이죠(장점). 그런데 두 요리사가 동시에 같은 소금통을 집으려 하거나, 한 명이 냄비 상태를 바꾸는 중에 다른 한 명이 끼어들면 요리가 엉망이 됩니다(race condition). 그래서 "이 도구는 한 번에 한 명만" 같은 규칙(동기화)이 필요해져요.

🙋 학생 질문 — "race condition이 정확히 뭐예요? 같은 데이터를 동시에 읽기만 하면 괜찮은 거 아니에요?"

맞아요, 읽기만 하면 대체로 괜찮습니다. 문제는 동시에 쓰기예요.

은행 계좌 잔액이 1000원이라고 합시다. 스레드 A와 B가 각각 100원씩 출금하려 해요. 정상이라면 800원이 남아야겠죠. 그런데 둘이 동시에 일하면 이렇게 꼬일 수 있어요. A가 잔액 1000원을 읽고, 거의 같은 순간 B도 1000원을 읽습니다(아직 A가 안 뺐으니까요). A는 "1000 − 100 = 900"을 써넣고, B도 자기가 읽은 1000으로 "1000 − 100 = 900"을 써넣어요. 200원이 빠져야 하는데 100원만 빠지고 잔액이 900원이 됩니다.

이렇게 여러 스레드의 실행 순서가 겹치면서 결과가 틀어지는 것이 race condition이에요. 막는 방법(뮤텍스·세마포어 같은 동기화 도구)은 동기화와 데드락을 다루는 B-4에서 제대로 배웁니다. 오늘은 "공유에는 이런 위험이 따라온다"까지만 잡고 가면 충분해요.

🎯 면접에서는 이렇게 답하세요

"멀티스레드의 장점은 응답성·자원 공유·경제성·확장성입니다. 한 스레드가 오래 걸리는 작업을 해도 다른 스레드가 사용자 입력을 받아 응답성이 좋고, 메모리를 공유해 데이터 교환이 빠르며, 생성·전환 비용이 프로세스보다 작고, 멀티코어에서 여러 코어로 퍼져 병렬 처리가 됩니다. 대가는 공유에서 옵니다. 여러 스레드가 공유 데이터에 동시에 접근하면 race condition이 생겨 이를 막는 동기화가 필요하고, 격리가 없어 한 스레드의 오류가 프로세스 전체를 죽일 수 있으며, 실행 순서가 비결정적이라 디버깅이 어렵습니다. 그래서 멀티스레드가 항상 빠른 건 아니고, 공유 비용과 동기화 비용까지 따져야 합니다."

💡 장점 중에 "스레드는 전환이 가볍다"가 있었죠. 도대체 그 전환 — 컨텍스트 스위칭 — 이 왜 비용이 들고, 왜 프로세스는 무겁고 스레드는 가벼울까요? 지난 시간에 "왜 그렇게 비싼지는 다음 시간에"라고 미뤄둔 숙제를 다음 Step에서 풉니다.


Step 4: "컨텍스트 스위칭 — 왜 그렇게 비싼가"

지난 시간에 컨텍스트 스위칭의 절반을 배웠어요. 운영체제가 프로세스 A를 멈추고 B로 갈아 끼울 때, A의 문맥(PC·레지스터)을 A의 PCB에 저장하고 B의 PCB에서 값을 꺼내 복원한다고요. 오늘은 나머지 절반, 왜 이게 비싼가를 봅니다. 면접에서 "컨텍스트 스위칭 비용"은 단골이에요.

먼저 컨텍스트 스위칭을 한 줄로 정리하면, 실행 중인 단위를 잠깐 멈추고 다른 단위로 CPU를 넘기면서, 현재 문맥을 저장하고 다음 문맥을 복원하는 것입니다. 그럼 비용은 어디서 올까요? 두 종류가 있어요.

첫째는 직접 비용입니다. 레지스터 값들을 메모리에 저장하고, 다음 단위의 값을 불러와 채우고, 스케줄러가 누구를 다음에 돌릴지 고르는 시간이에요. 이건 사실 그리 크지 않습니다.

진짜 비용은 둘째, 간접 비용이에요. 그리고 이게 프로세스와 스레드를 가릅니다. 프로세스를 바꾸면 메모리 주소공간이 통째로 바뀝니다. 그러면 A-2에서 배운 CPU 캐시가 문제가 돼요. 프로세스 A가 한참 도는 동안 캐시는 A가 자주 쓰는 데이터로 따뜻하게 채워져 있었는데, B로 전환하면 그 캐시 내용은 B에게 쓸모가 없습니다(주소공간이 다르니까요). B는 캐시 미스가 잔뜩 나서 한동안 느린 RAM을 자주 들여다봐야 해요. 캐시가 다시 따뜻해질 때까지 느려지는 거죠. 가상 주소를 실제 주소로 바꿔주는 TLB라는 캐시도 같이 비워집니다(TLB는 B-3에서 깊이 다뤄요).

그래서 프로세스 간 전환과 스레드 간 전환을 비교하면 이렇게 됩니다.

전환 시 하는 일 프로세스 간 전환 스레드 간 전환 (같은 프로세스)
레지스터·PC 저장/복원 한다 한다
메모리 주소공간 교체 한다 (무거움) 안 한다 (공유)
캐시·TLB 무효화 일어남 (다시 채워야 함) 대부분 유효
결과 무겁다 가볍다

같은 프로세스의 스레드끼리 전환할 때는 주소공간이 그대로예요. 코드·데이터·힙을 공유하니까요. 그래서 캐시·TLB가 대부분 유효한 채로 남고, 스택과 레지스터만 갈아 끼우면 됩니다. 이게 바로 "스레드가 프로세스보다 가볍다"의 진짜 정체예요. 메모리 공간을 안 바꿔도 되니까 가벼운 겁니다.

다만 스레드 전환도 공짜는 아니에요. 레지스터 저장·복원과 스케줄러 실행은 여전히 들어가니까요. 그래서 스레드를 수천 개씩 만들어 끊임없이 전환하면, 정작 일하는 시간보다 갈아 끼우는 시간이 더 커지는 일도 생깁니다.

🙋 학생 질문 — "캐시·TLB가 비워지면 왜 느려져요? 그게 컨텍스트 스위칭이랑 무슨 상관이에요?"

A-2에서 배운 캐시를 떠올려보세요. 캐시는 자주 쓰는 데이터를 CPU 가까이에 미리 담아둔 아주 빠른 저장소였죠. 캐시에 있으면(히트) 빠르고, 없으면(미스) 느린 RAM까지 다녀와야 했어요.

프로세스 A가 한참 도는 동안, 캐시는 A가 자주 쓰는 데이터로 가득 차서 적중률이 높아진 상태입니다. "따뜻해졌다"고 표현해요. 그런데 B로 전환하면, 캐시 안의 A 데이터는 B에게는 쓸모가 없어요. B는 주소공간이 다르니까요. 그래서 B는 처음에 캐시 미스를 잔뜩 내면서 느린 RAM을 자주 봐야 하고, 캐시가 B 데이터로 다시 따뜻해질 때까지 한동안 느립니다. 이 "캐시가 식어서 다시 데우는" 비용이 컨텍스트 스위칭의 진짜 무게예요.

스레드 간 전환은 주소공간이 같아서 캐시 내용이 그대로 유효합니다. 그래서 식지 않아요. 같은 비용을 안 치르니 가벼운 겁니다.

🎯 면접에서는 이렇게 답하세요

"컨텍스트 스위칭은 실행 중인 단위를 멈추고 다른 단위로 CPU를 넘기며 문맥(레지스터·PC)을 저장·복원하는 것입니다. 비용은 레지스터 저장·복원 같은 직접 비용보다, 프로세스를 바꿀 때 메모리 주소공간이 바뀌어 CPU 캐시와 TLB가 무효화되는 간접 비용이 큽니다. 새 프로세스는 캐시가 비어 한동안 캐시 미스로 느려지죠. 반면 같은 프로세스의 스레드끼리 전환하면 주소공간이 그대로라 캐시·TLB가 대부분 유효해, 스택·레지스터만 바꾸면 돼서 훨씬 가볍습니다. 이게 '스레드가 프로세스보다 가볍다'의 핵심입니다."

💡 컨텍스트 스위칭으로 운영체제가 여러 단위를 빠르게 번갈아 돌린다는 걸 봤어요. 그런데 코어가 하나면 "번갈아"지만, 코어가 여럿이면 "진짜 동시에"죠. 지난 시간 마지막에 약속한 그 정확한 이름 — 동시성과 병렬성을 다음 Step에서 만납니다.


Step 5: "동시성 vs 병렬성 — 번갈아 빠르게 vs 진짜 한꺼번에"

지난 시간 마지막 생각해볼 주제에서, 코어 하나로 "번갈아 빠르게" 도는 것과 코어 여럿으로 "진짜 한꺼번에" 도는 것이 겉보기엔 같아도 속은 다르다고 했죠. 그리고 "정확한 이름은 다음 시간에"라고 미뤄뒀어요. 그 이름이 동시성(concurrency)과 병렬성(parallelism)입니다. 면접 단골이라 둘을 칼같이 구분해둡시다.

  • 동시성(concurrency) — 여러 작업을 잘게 쪼개 번갈아 빠르게 처리해서, 동시에 진행되는 것처럼 보이게 하는 것. 코어가 하나여도 가능합니다. 어느 한 순간에 실제로 도는 건 하나뿐이지만, 전환이 워낙 빨라 동시처럼 보여요.
  • 병렬성(parallelism) — 여러 작업이 같은 순간에 진짜로 동시에 실행되는 것. 멀티코어가 있어야 가능합니다. 코어가 넷이면 네 작업이 진짜 동시에 돌죠.

핵심 구분은 이거예요. 동시성은 "여러 일을 동시에 다루는 구조"이고, 병렬성은 "여러 일을 같은 순간에 진짜 실행하는 것"입니다. 동시성은 싱글코어로도 되지만, 병렬성은 코어가 여럿이어야만 됩니다.

텍스트
   동시성 (1 core) — 번갈아 빠르게
   Core 1:  A B A B A B A B    시간     한 순간엔 하나, 잘게 번갈아

   병렬성 (2 cores) — 진짜 한꺼번에
   Core 1:  A A A A A A A A    시간     같은 순간에
   Core 2:  B B B B B B B B    시간     A와 B가 둘 다 실행

지난 시간에 쓴 요리사 비유를 이어볼게요. 동시성은 요리사 한 명이 세 냄비를 빠르게 오가며 젓는 거예요. 한 순간엔 한 냄비만 젓지만, 워낙 빨리 오가서 셋 다 동시에 익는 것처럼 보이죠. 병렬성은 요리사가 세 명이라 냄비 세 개를 정말 동시에 젓는 겁니다. 전자는 "빠르게 번갈아", 후자는 "정말 한꺼번에"예요.

🙋 학생 질문 — "그럼 싱글코어에선 병렬성이 아예 불가능해요? 멀티스레드 프로그램을 싱글코어에서 돌리면요?"

맞아요, 싱글코어에서는 병렬성이 불가능합니다. 코어가 하나면 같은 순간에 진짜로 도는 건 언제나 하나뿐이니까요.

멀티스레드 프로그램이라도 코어가 하나면, 운영체제가 스레드들을 아주 빠르게 번갈아 돌립니다. 이건 동시성이에요. 같은 프로그램을 쿼드코어에서 돌리면, 비로소 스레드들이 여러 코어로 퍼져 진짜 동시에 실행되죠. 이게 병렬성이고요.

그래서 "멀티스레드 = 병렬"이라고 외우면 틀려요. 정확히는 멀티스레드는 동시성을 위한 설계이고, 멀티코어를 만나야 병렬성으로 실현된다입니다. 동시성은 "어떻게 구조를 짤 것인가"의 문제고, 병렬성은 "그 구조가 하드웨어에서 실제로 펼쳐지는가"의 문제예요.

🎯 면접에서는 이렇게 답하세요

"동시성은 여러 작업을 잘게 쪼개 번갈아 처리해 동시에 진행되는 것처럼 보이게 하는 것으로, 코어가 하나여도 가능합니다. 어느 한 순간 실제 실행은 하나지만 전환이 빨라 동시처럼 보이죠. 병렬성은 멀티코어에서 여러 작업이 같은 순간에 진짜로 동시에 실행되는 것입니다. 한마디로 동시성은 '여러 일을 동시에 다루는 구조'이고 병렬성은 '여러 일을 같은 순간에 진짜 실행'입니다. 멀티스레드는 동시성을 위한 설계이고, 멀티코어를 만나야 병렬성으로 실현됩니다."

💡 동시성이든 병렬성이든, 코어 하나에는 Ready 줄에 선 여러 실행 단위 중 누구를 먼저 올릴지를 누군가 정해야 합니다. 지난 시간에 본 상태 전이도의 Ready → Running, 그 순서를 정하는 게 CPU 스케줄링이에요. 여기서부터 오늘 수업의 후반부, 스케줄링으로 들어갑니다.


Step 6: "CPU 스케줄링이란 — 누구에게 CPU를 줄까"

지난 시간에 그렸던 프로세스 상태 전이도, 기억하시죠? 거기서 약속한 대로 그 그림이 오늘 다시 주인공으로 등장합니다. 핵심 장면은 Ready → Running이에요. CPU는 (코어 수만큼) 하나뿐인데 Ready 줄에 선 실행 단위는 여럿입니다. 그중 누구를 골라 Running으로 올릴까 — 이걸 정하는 게 CPU 스케줄링이에요.

역할을 둘로 나눠 부릅니다. 다음에 누구를 실행할지 고르는 운영체제 부분이 스케줄러(scheduler), 실제로 그 단위에게 CPU를 넘겨주는(컨텍스트 스위칭을 수행하는) 부분이 디스패처(dispatcher)예요. 스케줄링은 상태 전이가 일어나는 순간마다 작동합니다. 어떤 단위가 I/O 대기로 빠지거나(Running → Waiting), 시간을 다 쓰거나(Running → Ready), 끝나거나(Terminated) 할 때마다 "그럼 다음은 누구지?"를 정해야 하니까요.

텍스트
   [Ready 큐]  P1 · P2 · P3 · P4   ──── CPU 받기를 기다리며 줄 섬
        │
        │  스케줄러가 하나를 고름  디스패처가 CPU를 넘김
        ▼
   [Running]   P2 실행 중           ──── 한 번에 하나 (코어 수만큼)
        │
        ├─ 시간 다 씀 (선점) ──────▶ 다시 [Ready] 줄 맨 뒤로
        ├─ I/O 대기 ──────────────▶ [Waiting] 으로
        └─ 종료 ──────────────────▶ [Terminated]

여기서 큰 갈래가 하나 갈립니다. CPU를 강제로 빼앗을 수 있느냐예요.

  • 비선점(non-preemptive) 스케줄링 — 한 번 CPU를 받은 단위가 스스로 내놓을 때까지(I/O 대기로 빠지거나 끝날 때까지) 운영체제가 뺏지 않습니다. 단순하고 전환이 적지만, 긴 작업이 CPU를 오래 쥐면 뒤에 선 짧은 작업들이 하염없이 기다려야 해요.
  • 선점(preemptive) 스케줄링 — 운영체제가 타이머 인터럽트로 CPU를 강제로 회수해 다른 단위에게 넘깁니다(A-2·B-1에서 본 그 타이머 인터럽트예요!). 응답성이 좋지만, 컨텍스트 스위칭이 잦아 비용이 쌓이고, 공유 데이터를 건드리던 중에 끊기면 충돌 위험이 커집니다.

그럼 스케줄러는 무엇을 잘하려는 걸까요? 좋은 스케줄링인지를 재는 평가지표가 있습니다.

  • CPU 이용률(utilization) — CPU를 놀리지 않고 얼마나 바쁘게 굴리는가.
  • 처리량(throughput) — 단위 시간당 끝낸 작업 수.
  • 반환시간(turnaround time) — 작업이 도착해서 완전히 끝날 때까지의 총 시간.
  • 대기시간(waiting time) — Ready 줄에서 기다린 시간의 합.
  • 응답시간(response time) — 도착한 뒤 처음 CPU를 받기까지 걸린 시간.

중요한 건, 이 지표들을 동시에 다 최적화할 수는 없다는 거예요. 상황마다 중시하는 게 다릅니다. 밤새 대량 작업을 돌리는 배치 시스템은 처리량이 중요하고, 사람이 클릭하면 바로바로 반응해야 하는 대화형 시스템은 응답시간이 중요하죠. 스케줄링 알고리즘마다 어떤 지표에 강하고 어떤 지표에 약한지가 갈립니다.

🙋 학생 질문 — "선점이 응답성이 좋으면 무조건 선점이 낫지 않아요? 왜 비선점을 써요?"

선점도 대가가 있어요. 두 가지예요.

첫째, 선점은 타이머로 자주 갈아 끼우니 컨텍스트 스위칭 비용(Step 4의 그 비용)이 계속 쌓입니다. 너무 자주 빼앗으면 정작 일하는 시간보다 전환에 쓰는 시간이 늘어요.

둘째, 공유 데이터를 건드리던 중에 강제로 끊기면 충돌 위험이 커집니다. 한 단위가 데이터를 절반쯤 고치다 끊겼는데 다른 단위가 끼어들면, Step 3에서 본 race condition이 나기 쉬워져요.

그래서 단순하고 충돌 걱정이 적은 환경(예: 정해진 순서로 도는 일부 배치 작업)에서는 비선점이 더 깔끔할 수 있습니다. 다만 사람이 직접 쓰는 데스크톱·서버 같은 현대 범용 운영체제는 응답성이 중요해서 대부분 선점을 씁니다. 역시 트레이드오프예요.

🎯 면접에서는 이렇게 답하세요

"CPU 스케줄링은 Ready 큐에 있는 여러 실행 단위 중 누구에게 CPU를 줄지 정하는 것입니다. 고르는 정책이 스케줄러, 실제로 CPU를 넘기는 게 디스패처죠. 비선점 방식은 한 번 CPU를 받으면 스스로 내놓을 때까지 뺏지 않아 단순하지만 긴 작업이 뒤를 막고, 선점 방식은 타이머 인터럽트로 강제로 CPU를 회수해 응답성이 좋지만 컨텍스트 스위칭이 잦습니다. 스케줄러는 CPU 이용률·처리량·반환시간·대기시간·응답시간 같은 지표로 평가되는데, 이 지표들을 동시에 다 최적화할 수는 없어서, 배치 시스템은 처리량을, 대화형 시스템은 응답시간을 중시하는 식으로 목표가 달라집니다."

💡 그럼 스케줄러는 구체적으로 어떤 기준으로 다음 단위를 고를까요? 가장 단순한 "온 순서대로"부터, 짧은 것 먼저, 시간을 잘라 돌아가며 — 여러 방법이 있어요. 다음 두 Step에서 대표 알고리즘들을 간트 차트로 비교합니다. 먼저 도착 순서와 작업 길이로 줄 세우는 방법부터.


Step 7: "스케줄링 알고리즘 ① — 도착·길이로 줄 세우기"

알고리즘은 간트 차트로 보면 한눈에 들어와요. 작업들이 CPU를 언제부터 언제까지 쓰는지 시간 막대로 그리는 그림이에요. 먼저 도착 순서나 작업 길이를 기준으로 줄 세우는 세 가지를 봅시다.

FCFS(First-Come, First-Served, 선착순)는 가장 단순해요. 도착한 순서대로 처리합니다. 비선점이고, 줄 서면 순서대로 받으니 공정해 보이죠. 그런데 함정이 있어요. 긴 작업이 앞에 오면, 뒤에 온 짧은 작업들이 전부 그 긴 작업이 끝나길 기다려야 합니다. 이걸 호위 효과(convoy effect)라고 해요. 은행 창구에서 앞사람이 100건을 처리하는 동안 뒤의 1건짜리 손님도 한참 기다리는 상황이죠.

SJF(Shortest Job First, 최단 작업 우선)는 실행 시간이 짧은 작업을 먼저 처리합니다. 비선점이고, 짧은 걸 먼저 보내니 평균 대기시간이 이론상 가장 짧아요. 같은 작업들이라도 순서만 바꾸면 평균 대기가 확 줄어듭니다. 예를 들어 실행 시간이 P1=7, P2=3, P3=2인 작업 셋이 동시에 도착했다고 해볼게요.

텍스트
   FCFS (도착 순 P1  P2  P3):
   | P1            | P2      | P3   |
   0               7         10     12
   평균 대기 = (0 + 7 + 10) / 3 = 5.7

   SJF (짧은 순 P3  P2  P1):
   | P3   | P2      | P1            |
   0      2         5              12
   평균 대기 = (0 + 2 + 5) / 3 = 2.3

똑같은 작업인데 순서만 바꿨더니 평균 대기가 5.7에서 2.3으로 줄었죠. 짧은 걸 앞으로 보내면 여러 작업이 덜 기다린다는 게 눈으로 보입니다. 그런데 SJF도 문제가 있어요. 짧은 작업이 계속 들어오면 긴 작업은 영영 차례가 안 와 굶을 수 있고(기아, starvation), 애초에 작업이 얼마나 걸릴지 미리 알기 어렵습니다.

SRT(Shortest Remaining Time, 최단 잔여 시간)는 SJF의 선점 버전이에요. 실행 중에 더 짧은 작업이 새로 도착하면, 현재 작업을 멈추고 그 짧은 걸 먼저 처리합니다. 평균 대기는 SJF보다 더 줄지만, 작업이 도착할 때마다 끼어들 수 있어 컨텍스트 스위칭이 잦아지고, 긴 작업이 굶는 기아 문제는 여전합니다.

🙋 학생 질문 — "SJF가 평균 대기시간이 최소라면서요? 그럼 그냥 SJF 쓰면 되는 거 아니에요?"

두 가지 현실적인 벽에 부딪혀요.

첫째, 실행 시간을 미리 알 수 없습니다. SJF는 "이 작업이 3초, 저 작업이 7초 걸린다"를 알아야 짧은 걸 먼저 보내죠. 그런데 작업이 실제로 얼마나 걸릴지는 끝나봐야 압니다. 과거 기록으로 추정할 뿐, 정확히는 몰라요.

둘째, 기아입니다. 짧은 작업이 계속 들어오면, 긴 작업은 "다음, 다음" 밀리다가 영영 차례가 안 올 수 있어요. 평균은 좋아도 특정 작업이 굶어버리는 불공정이 생기는 거죠.

그래서 평균 대기만 좋다고 답이 아니에요. 이 기아와 예측 문제를 피하려고, 시간을 잘게 잘라 모두에게 돌아가며 공평하게 주는 방법이 나옵니다. 다음 Step의 라운드 로빈이에요.

🎯 면접에서는 이렇게 답하세요

"FCFS는 도착 순서대로 처리하는 가장 단순한 비선점 방식인데, 긴 작업이 앞에 오면 뒤의 짧은 작업들이 한참 기다리는 호위 효과가 단점입니다. SJF는 실행 시간이 짧은 작업을 먼저 처리해 평균 대기시간이 이론상 최소지만, 실행 시간을 미리 알기 어렵고 긴 작업이 계속 밀리는 기아 문제가 있습니다. SRT는 SJF의 선점 버전으로, 더 짧은 작업이 도착하면 현재 작업을 멈추고 그것부터 처리합니다. 평균 대기는 더 줄지만 전환이 잦고 기아는 여전합니다."

💡 SJF의 기아와 예측 문제를 피하려면, 짧고 김을 따지지 말고 모두에게 시간을 조금씩 돌아가며 주면 됩니다. 그게 라운드 로빈이에요. 거기에 우선순위와 MLFQ까지, 시간과 우선순위를 기준으로 돌려 쓰는 알고리즘들을 마지막 Step에서 봅니다.


Step 8: "스케줄링 알고리즘 ② — 시간·우선순위로 돌려 쓰기"

이번엔 작업 길이 대신, 시간을 잘라 돌리거나 우선순위를 매기는 방법들이에요. 그중 라운드 로빈은 면접에서 가장 자주 나오는 스케줄링 알고리즘이라 꼭 잡고 갑니다.

라운드 로빈(Round Robin, RR)은 시간을 타임 퀀텀(time quantum, 또는 타임 슬라이스)이라는 작은 조각으로 자릅니다(예: 10밀리초). 그리고 각 작업에게 한 조각씩 돌아가며 CPU를 줘요. 한 조각 안에 다 못 끝낸 작업은 Ready 줄 맨 뒤로 가서 다시 차례를 기다립니다. 선점 방식이죠. 예를 들어 P1=5, P2=3, P3=1인 작업이 동시에 도착하고 타임 퀀텀이 2라면 이렇게 돕니다.

텍스트
   라운드 로빈 (타임 퀀텀 = 2):
   | P1 | P2 | P3 | P1 | P2 | P1 |
   0    2    4    5    7    8    9
   P3는 1만에 끝, P1·P2는 돌아가며 조금씩 진행

모두가 일정 시간 안에 한 번씩은 CPU를 받으니 기아가 없고 응답성이 좋습니다. 그래서 사람이 직접 쓰는 대화형 시스템에 잘 맞아요. 핵심은 타임 퀀텀 크기예요. 너무 크면 한 작업이 오래 쥐고 있어 사실상 FCFS처럼 돼 응답성이 떨어지고, 너무 작으면 자주 갈아 끼우느라 컨텍스트 스위칭 오버헤드(Step 4의 그 비용)가 커집니다. 그래서 "전환 비용은 무시할 만큼 작되 응답성은 살아있을 만큼"의 절충점(보통 10~100밀리초)을 고릅니다.

우선순위(Priority) 스케줄링은 각 작업에 우선순위를 매겨 높은 것 먼저 처리합니다. 선점·비선점 둘 다 가능해요. 직관적이지만, 낮은 우선순위 작업이 계속 밀려 굶는 기아가 또 문제예요. 해결책은 에이징(aging) — 오래 기다린 작업의 우선순위를 시간이 갈수록 조금씩 올려줘서, 언젠가는 반드시 차례가 오게 만드는 겁니다.

MLFQ(Multi-Level Feedback Queue, 다단계 피드백 큐)는 앞의 아이디어들을 영리하게 합친 거예요. Ready 큐를 우선순위별로 여러 층 두고, 작업이 행동하는 걸 보고 층을 옮깁니다. CPU를 오래 통째로 쓰는 작업은 아래층(낮은 우선순위)으로 내리고, 짧게 쓰고 자주 I/O로 빠지는(대화형으로 보이는) 작업은 위층(높은 우선순위)에 둬요. 이렇게 하면 실행 시간을 미리 몰라도 행동만 보고 "이건 짧은 작업이구나"를 추측해 SJF 비슷한 효과를 내면서, 에이징으로 기아도 막습니다. 현대 운영체제 스케줄러가 바탕에 깔고 있는 아이디어예요.

🙋 학생 질문 — "타임 퀀텀을 아주 작게 하면 모두가 빨리빨리 돌아가니까 좋은 거 아니에요?"

작게 할수록 응답은 빨라 보이지만, 함정이 있어요. 컨텍스트 스위칭 비용이에요.

타임 퀀텀이 작으면 그만큼 자주 갈아 끼워야 하죠. 그런데 한 번 갈아 끼울 때마다 Step 4에서 본 전환 비용(레지스터 저장·복원, 캐시 식음)이 듭니다. 극단적으로 타임 퀀텀이 전환 비용과 비슷해지면, CPU가 일하는 시간만큼을 전환에 낭비해요. 일하다 끊고, 갈아 끼우고, 또 일하다 끊고… 효율이 반토막 나는 거죠.

반대로 너무 크면 라운드 로빈이 사실상 FCFS가 돼서 응답성이 죽습니다. 그래서 "전환 비용은 무시할 만큼은 크되, 응답성은 살아있을 만큼은 작게"의 가운데를 찾습니다. Step 4에서 배운 컨텍스트 스위칭 비용이 여기서 타임 퀀텀 크기를 정하는 직접적인 근거가 돼요.

🎯 면접에서는 이렇게 답하세요

"라운드 로빈은 시간을 타임 퀀텀이라는 작은 조각으로 잘라 각 작업에 돌아가며 한 조각씩 주는 선점 방식입니다. 모두가 일정 시간 안에 CPU를 받아 기아가 없고 응답성이 좋아 대화형 시스템에 적합하죠. 타임 퀀텀이 너무 크면 FCFS처럼 되고 너무 작으면 컨텍스트 스위칭 오버헤드가 커져, 적절한 크기 선택이 중요합니다. 우선순위 스케줄링은 우선순위 높은 작업을 먼저 처리하되 낮은 작업이 굶지 않도록 에이징으로 우선순위를 점점 올려줍니다. MLFQ는 여러 우선순위 큐를 두고 작업의 행동을 보며 큐를 옮겨, 실행 시간을 미리 몰라도 짧고 대화형인 작업을 우대하는 현대 스케줄러의 바탕 아이디어입니다."

💡 스레드부터 스케줄링까지 운영체제의 실행 단위를 한 바퀴 돌았어요. 가벼운 실행 단위인 스레드가 무엇을 공유하고, 그걸 번갈아·동시에 돌리는 게 무슨 뜻이며, CPU를 어떤 순서로 나눠주는지까지요. 정리하고 다음 시간 다리를 놓을게요.


마무리

오늘 배운 핵심 세 가지

🎯 하나, 스레드는 프로세스 안에서 코드·데이터·힙을 공유하고 스택·레지스터만 따로 갖는 가벼운 실행 단위입니다. 프로세스가 자원을 소유하는 단위라면 스레드는 그 위에서 실행되는 단위예요. 멀티스레드는 응답성·자원 공유·경제성·확장성을 얻는 대신, 공유 데이터 충돌(race condition)과 "한 스레드가 죽으면 프로세스 전체가 위험"이라는 대가를 치릅니다.

🎯 , 컨텍스트 스위칭은 문맥(레지스터·PC)을 저장·복원하는 일인데, 진짜 비용은 프로세스를 바꿀 때 메모리 주소공간이 교체되어 캐시·TLB가 식는 간접 비용입니다. 그래서 주소공간을 공유하는 스레드 간 전환은 가볍죠. 그리고 동시성(번갈아 빠르게, 싱글코어로도 가능)과 병렬성(진짜 동시, 멀티코어 필수)은 본질적으로 다릅니다.

🎯 , CPU 스케줄링은 Ready 줄에 선 단위 중 누구에게 CPU를 줄지 정하는 것입니다. 선점(타이머로 회수)과 비선점(스스로 내놓을 때까지)으로 나뉘고, 대표 알고리즘으로 FCFS(호위 효과)·SJF(평균 대기 최소·기아·예측 문제)·라운드 로빈(타임 퀀텀·공정)·우선순위(에이징)·MLFQ(행동 기반)가 있어요. 대기·응답·반환시간·처리량 같은 지표로 평가합니다.

다음 시간 예고

오늘 컨텍스트 스위칭을 이야기하면서 "프로세스를 바꾸면 메모리 주소공간을 통째로 바꾼다"는 말을 여러 번 했죠. 그런데 그 "주소공간"이 정확히 뭘까요? 프로세스마다 자기만의 메모리를 통째로 가진다는데, 실제 RAM은 한 덩어리뿐인데 어떻게 그게 가능할까요?

다음 시간(B-3)에는 그 비밀, 가상 메모리를 만납니다. 프로세스가 보는 메모리 주소는 사실 진짜 주소가 아니라 운영체제가 만들어준 가짜 주소라는 것, 그 가짜 주소를 진짜 주소로 바꾸는 페이징과 오늘 잠깐 나온 TLB, 그리고 메모리가 부족할 때 벌어지는 페이지 교체스래싱까지요. B-1에서 본 스택과 힙이 실제로는 이 가상 주소 위에 놓여 있었다는 이야기도 거기서 이어집니다. 그리고 오늘 멀티스레드에서 잠깐 만난 race condition을 제대로 막는 동기화(뮤텍스·세마포어)와 데드락은 그다음(B-4)에서 다뤄요.


과제

[기초] 간트 차트 그려 평균 대기시간 비교하기

연필과 종이만 있으면 됩니다. 작업 셋이 동시에 도착했고, 실행 시간이 각각 P1 = 6, P2 = 2, P3 = 4라고 합시다.

  1. FCFS(도착 순 P1 → P2 → P3)로 간트 차트를 그리고, 각 작업의 대기시간(자기가 시작되기 전까지 기다린 시간)과 평균 대기시간을 계산해보세요.
  2. SJF(짧은 순)로 다시 간트 차트를 그리고 평균 대기시간을 계산해, FCFS와 비교해보세요. 어느 쪽이 평균 대기가 짧은가요?
  3. 이 예에서 SJF가 더 좋게 나왔다면, 그럼에도 현실에서 SJF를 그냥 쓰기 어려운 이유 두 가지(예측·기아)를 한 줄씩 적어보세요.

[응용] 내 컴퓨터의 스레드 들여다보기

자기 컴퓨터에서 스레드를 관찰하는 과제입니다. 설치할 건 없어요.

  1. 작업 관리자(윈도우, 세부 정보 탭에서 "스레드" 열을 추가) 또는 활성 상태 보기(맥)를 열어, 한 프로세스가 스레드를 몇 개나 갖고 있는지 확인해보세요. 브라우저처럼 무거운 프로그램은 스레드가 수십 개일 거예요.
  2. 같은 프로그램 안에서 스레드가 여러 개인 이유를, 오늘 배운 멀티스레드의 장점(응답성·자원 공유 등)으로 한 문단 설명해보세요.
  3. 큰 파일을 다운로드하면서 동시에 그 프로그램의 다른 기능(스크롤·메뉴 클릭)이 멈추지 않고 되는지 체감해보고, 이게 어떤 장점 덕분인지 한 줄로 적어보세요.

[심화] "프로세스 vs 스레드" 면접 답변과 꼬리 질문 대비하기

조사하고 정리하는 과제입니다.

  1. "프로세스와 스레드의 차이를 설명해보세요"에 대한 자기만의 답변을 메모리·통신·비용·안정성 네 항목으로 정리해 적어보세요.
  2. 이어질 법한 꼬리 질문 세 개 — "왜 스레드가 프로세스보다 가벼운가요?", "race condition이 뭔가요?", "멀티스레드면 항상 빠른가요?" — 에 대한 답을 각각 두세 문장으로 준비해보세요.
  3. (선택) "동시성과 병렬성의 차이"를 코어 개수와 연결해 한 문단으로 정리해, 위 답변 뒤에 자연스럽게 이어 붙여보세요.

생각해볼 주제

1. 스레드는 항상 빠르고 좋은 선택일까?

스레드는 가볍고, 메모리를 공유해 빠르고, 멀티코어에서 병렬로 퍼질 수 있다고 배웠습니다. 그렇다면 일을 잘게 쪼개 스레드를 많이 만들수록 항상 빨라질까요? 그런데 스레드도 컨텍스트 스위칭 비용이 들고(완전히 공짜는 아니죠), 공유 데이터를 안전하게 다루려면 동기화 비용이 따라붙습니다. 스레드를 수천 개 만들면 무슨 일이 벌어질지, "일을 더 잘게 쪼개는 것"과 "쪼갠 걸 관리하는 비용" 사이의 균형을 생각해보세요.

2. 라운드 로빈의 타임 퀀텀, 크게 vs 작게

라운드 로빈에서 타임 퀀텀을 너무 크게 하면 FCFS처럼 되고, 너무 작게 하면 컨텍스트 스위칭 오버헤드가 커진다고 했습니다. 그렇다면 사람이 직접 쓰는 대화형 시스템(키 입력에 바로 반응해야 함)과, 밤새 대량 연산을 돌리는 배치 시스템(끝까지 빨리 끝내는 게 중요함)은 각각 타임 퀀텀을 크게 두는 게 나을까요, 작게 두는 게 나을까요? 두 시스템이 중시하는 평가지표(응답시간 vs 처리량)와 연결해 생각해보세요.

3. 멀티코어 시대에 동시성 설계가 왜 점점 더 중요해졌을까?

예전에는 CPU 하나의 속도(클럭)가 해마다 빨라져서, 같은 프로그램도 새 컴퓨터에서 그냥 더 빨라졌습니다. 그런데 A-2에서 본 발열의 벽 때문에 클럭 높이기가 한계에 부딪히자, 제조사들은 코어 수를 늘리는 쪽으로 방향을 틀었어요. 문제는, 일을 한 줄로만 처리하도록 짠 프로그램은 코어가 8개여도 1개만 쓰고 나머지 7개를 놀린다는 점입니다. 이 변화가 "프로그램을 어떻게 짜야 하는가"에 어떤 숙제를 안겼는지, 동시성과 병렬성을 연결해 생각해보세요.

✅ 예시 답안정답 보기

과제 예시답안

🎯 [과제 1 예시답안] 간트 차트 그려 평균 대기시간 비교하기

채점 포인트

항목 배점 기준
FCFS 간트·대기시간 35% 도착 순서대로 그리고 각 작업의 대기시간·평균을 올바로 계산했는가
SJF 간트·대기시간 35% 짧은 순으로 그리고 평균을 계산해 FCFS와 비교했는가
SJF의 현실적 한계 30% 예측 어려움·기아 두 가지를 짚었는가

풀이 예시

작업은 P1 = 6, P2 = 2, P3 = 4로, 셋 다 0에 도착했습니다.

1. FCFS (도착 순 P1 → P2 → P3)

텍스트
   | P1            | P2   | P3       |
   0               6      8          12
   대기: P1 = 0, P2 = 6, P3 = 8
   평균 대기 = (0 + 6 + 8) / 3 = 4.67

각 작업의 대기시간은 "자기가 시작되기 전까지 기다린 시간"이에요. P1은 바로 시작하니 0, P2는 P1이 끝나는 6까지 기다려서 6, P3은 P1·P2가 끝나는 8까지 기다려서 8입니다.

2. SJF (짧은 순 P2 → P3 → P1)

텍스트
   | P2   | P3       | P1            |
   0      2          6              12
   대기: P2 = 0, P3 = 2, P1 = 6
   평균 대기 = (0 + 2 + 6) / 3 = 2.67

짧은 작업(P2 = 2)을 맨 앞으로 보내니, 뒤 작업들이 기다리는 시간이 줄었어요. 평균 대기가 4.67 → 2.67로 짧아졌습니다. SJF가 평균 대기에 유리하다는 게 숫자로 확인됩니다.

3. SJF를 그냥 쓰기 어려운 이유

  • 예측 문제 — SJF는 각 작업이 얼마나 걸릴지 미리 알아야 짧은 걸 먼저 보냅니다. 그런데 실행 시간은 끝나봐야 알아요. 과거 기록으로 추정할 뿐 정확히는 알 수 없습니다.
  • 기아(starvation) — 짧은 작업이 계속 들어오면 긴 작업(P1 같은)은 계속 뒤로 밀려 영영 차례가 안 올 수 있습니다. 평균은 좋아도 특정 작업이 굶는 불공정이 생겨요.

💡 튜터의 포인트

이 과제의 핵심은 "순서만 바꿔도 평균 대기가 달라진다"를 손으로 직접 계산해 체감하는 거예요. 간트 차트는 면접에서 화이트보드에 그려보라고 할 수도 있는 도구라, 한 번 그려두면 자신감이 붙습니다. 그리고 "SJF가 최적인데 왜 안 쓰냐"는 꼬리 질문이 자주 나오는데, 답은 항상 예측 불가기아 두 가지예요. 이 두 한계가 바로 다음에 배운 라운드 로빈과 MLFQ가 나온 이유라는 흐름까지 연결해두면, 스케줄링 전체가 하나의 이야기로 묶입니다.


🎯 [과제 2 예시답안] 내 컴퓨터의 스레드 들여다보기

채점 포인트

항목 배점 기준
스레드 개수 확인 35% 한 프로세스의 스레드 개수를 실제로 확인했는가
멀티스레드 장점 설명 35% 스레드가 여러 개인 이유를 응답성·자원 공유 등으로 설명했는가
응답성 체감 30% 다운로드 중에도 다른 기능이 되는 걸 응답성으로 연결했는가

풀이 예시

확인 방법(설치 불요):

  • 윈도우: 작업 관리자(Ctrl+Shift+Esc) → 세부 정보 탭 → 열 머리글 우클릭 → "스레드" 열 추가.
  • 맥: 활성 상태 보기(응용 프로그램 → 유틸리티) → 프로세스 더블클릭 → 스레드 수 확인.

관찰 예시:

텍스트
   chrome           스레드 38개
   Code (편집기)     스레드 51개
   탐색기/Finder     스레드 22개
   ...
   하나의 프로그램인데 스레드가 수십 개씩!

"왜 스레드가 여러 개인가" 설명 예시:

한 프로그램이 여러 일을 동시에 처리하려고 스레드를 여러 개 둡니다. 예를 들어 브라우저는 화면을 그리는 스레드, 네트워크로 데이터를 받는 스레드, 사용자 입력을 받는 스레드 등을 따로 둬요. 이렇게 나누면 한 스레드가 무거운 일을 해도 다른 스레드가 계속 돌아 응답성이 좋고, 같은 메모리를 공유하니 데이터를 주고받기도 빠릅니다.

"응답성 체감" 설명 예시:

큰 파일을 다운로드하는 동안에도 스크롤과 메뉴 클릭이 멈추지 않았습니다. 다운로드를 맡은 스레드와 화면·입력을 맡은 스레드가 따로 돌기 때문이에요. 만약 단일 스레드였다면 다운로드가 끝날 때까지 화면이 멈췄을 겁니다. 이게 멀티스레드의 응답성 장점입니다.

💡 튜터의 포인트

교과서 속 "한 프로세스 안에 여러 스레드"가 내 컴퓨터에 진짜로 있다는 걸 눈으로 보는 게 핵심이에요. 특히 브라우저나 편집기가 스레드를 수십 개씩 쓰는 걸 보면, Step 1에서 배운 "프로세스 안의 여러 실행 흐름"이 한 번에 와닿습니다. 참고로 스레드를 코드로 직접 만들고 다루는 법(스레드 API)은 java-basic 같은 언어 과목에서 실전으로 배워요. 여기서는 "왜 이렇게 여러 개가 도는지" 원리를 아는 데까지면 충분합니다.


🎯 [과제 3 예시답안] "프로세스 vs 스레드" 면접 답변과 꼬리 질문 대비하기

채점 포인트

항목 배점 기준
4항목 비교 답변 40% 메모리·통신·비용·안정성으로 프로세스와 스레드를 구분했는가
꼬리 질문 3개 45% 가벼운 이유·race condition·항상 빠른가에 원리로 답했는가
동시성/병렬성 연결 15% (선택) 코어 개수와 연결해 정리했는가

풀이 예시

1. "프로세스와 스레드의 차이" 답변

프로세스는 각자 독립된 메모리 공간을 가진 자원 소유의 단위이고, 스레드는 한 프로세스 안에서 코드·데이터·힙을 공유하며 실행되는 CPU 흐름의 단위입니다. 메모리는 프로세스가 전부 따로, 스레드는 스택·레지스터만 따로 갖습니다. 통신은 프로세스가 IPC를 거쳐야 하지만 스레드는 공유 메모리를 직접 읽고 써 빠릅니다. 비용은 스레드의 생성·전환이 프로세스보다 가볍습니다. 안정성은 프로세스가 격리돼 하나가 죽어도 안전하지만, 스레드는 하나가 죽으면 프로세스 전체가 위험합니다.

2. 꼬리 질문 대비

"왜 스레드가 프로세스보다 가벼운가요?" — 컨텍스트 스위칭 때문입니다. 프로세스를 바꾸면 메모리 주소공간이 통째로 바뀌어 캐시·TLB가 무효화돼 무겁지만, 같은 프로세스의 스레드끼리는 주소공간이 그대로라 스택·레지스터만 바꾸면 돼서 가볍습니다.

"race condition이 뭔가요?" — 여러 스레드가 공유 데이터에 동시에 접근(특히 쓰기)할 때 실행 순서가 겹치면서 결과가 틀어지는 현상입니다. 예를 들어 두 스레드가 같은 잔액을 동시에 읽고 각자 빼서 쓰면, 한 번만 빠진 것처럼 값이 꼬입니다. 막으려면 동기화가 필요합니다.

"멀티스레드면 항상 빠른가요?" — 아닙니다. 스레드도 컨텍스트 스위칭 비용이 들고, 공유 데이터를 안전하게 다루려면 동기화 비용이 따라붙습니다. 스레드를 너무 많이 만들면 전환과 동기화에 드는 비용이 이득을 넘어설 수 있어요. 또 일이 한 줄로 이어져 쪼갤 수 없으면 스레드를 늘려도 빨라지지 않습니다.

3. (선택) 동시성/병렬성 연결

덧붙이면, 멀티스레드는 동시성을 위한 설계입니다. 코어가 하나면 스레드들을 번갈아 돌리는 동시성에 그치고, 코어가 여럿이어야 여러 스레드가 여러 코어에 퍼져 진짜 동시에 도는 병렬성으로 실현됩니다.

💡 튜터의 포인트

"프로세스와 스레드의 차이"는 운영체제 면접 1순위 질문이라, 항목으로 끊어 답하는 연습이 핵심이에요. 메모리·통신·비용·안정성 네 칸으로 외워두면, 긴장해도 빠뜨리지 않고 답할 수 있습니다. 그리고 면접관은 거의 항상 "왜 가벼운가"로 파고드는데, 여기서 컨텍스트 스위칭과 캐시까지 끌어오면 한 단계 깊은 이해를 보여줄 수 있어요. 외운 한 줄이 아니라 원리로 이어 말하는 게, 이 과목이 노리는 답변입니다.


생각해볼 주제 예시답안

🤔 [생각해볼 주제 1 예시답안] 스레드는 항상 빠르고 좋은 선택일까?

[문제 상황 요약]

스레드는 가볍고, 메모리를 공유해 빠르고, 멀티코어에서 병렬로 퍼질 수 있습니다. 그렇다면 일을 잘게 쪼개 스레드를 많이 만들수록 항상 빨라질까요? 스레드도 컨텍스트 스위칭 비용이 들고, 공유 데이터를 안전하게 다루려면 동기화 비용이 따라붙습니다.

[튜터의 가이드 및 해설]

"더 많이 = 더 빠르게"가 항상 성립하지 않는다는 걸 보는 질문이에요. 스레드를 늘릴 때 얻는 것과 따라붙는 비용을 같이 봐야 합니다.

얻는 것 — 일을 여러 갈래로 쪼개면, 한 스레드가 오래 걸리는 작업을 하는 동안 다른 스레드가 일을 진행할 수 있습니다(응답성). 코어가 여럿이면 여러 코어에 퍼져 진짜 동시에 처리돼 빨라지기도 하고요(병렬성).

따라붙는 비용 — 그런데 스레드도 공짜가 아니에요. 첫째, 스레드가 많아질수록 운영체제가 갈아 끼우는 횟수가 늘어 컨텍스트 스위칭 비용이 쌓입니다. 스레드 수천 개를 만들어 코어 몇 개에 욱여넣으면, 정작 일하는 시간보다 갈아 끼우는 시간이 더 커질 수 있어요. 둘째, 스레드들이 공유 데이터를 안전하게 쓰려면 동기화가 필요한데, 한 번에 하나만 들어가게 줄을 세우는 그 동기화가 오히려 병렬성을 깎아먹습니다. 모두가 같은 자물쇠 앞에 줄을 서면, 코어가 많아도 한 줄로 처리되는 셈이죠.

그래서 적정선이 있다 — 스레드 수는 보통 코어 수와 작업의 성격(CPU를 많이 쓰는 일인지, I/O를 기다리는 일이 많은지)에 맞춰 정합니다. 무작정 많이 만드는 게 아니라, 쪼개서 얻는 이득이 전환·동기화 비용을 넘는 지점까지만 늘리는 거예요. 또 애초에 일이 한 줄로 이어져 쪼갤 수 없으면(앞 결과가 있어야 뒤를 계산하는 경우), 스레드를 늘려도 빨라지지 않습니다.

핵심은 "병렬화에는 한계 비용이 있다"예요. 일을 쪼개는 이득과, 쪼갠 걸 관리하는 비용 사이의 균형점을 찾는 게 멀티스레드 설계의 핵심입니다.

🎯 면접관을 홀리는 핵심 멘트

"스레드를 늘린다고 항상 빨라지지는 않습니다. 스레드도 컨텍스트 스위칭 비용이 들어서, 너무 많으면 일하는 시간보다 갈아 끼우는 시간이 커집니다. 게다가 공유 데이터를 안전하게 다루려면 동기화가 필요한데, 모두가 같은 자물쇠 앞에 줄을 서면 코어가 많아도 사실상 한 줄로 처리돼 병렬성이 깎입니다. 그래서 스레드 수는 코어 수와 작업 성격(CPU 위주냐 I/O 위주냐)에 맞춰 적정선을 찾고, 애초에 쪼갤 수 없는 일은 스레드를 늘려도 빨라지지 않습니다. 병렬화는 이득과 관리 비용의 트레이드오프입니다."


🤔 [생각해볼 주제 2 예시답안] 라운드 로빈의 타임 퀀텀, 크게 vs 작게

[문제 상황 요약]

라운드 로빈에서 타임 퀀텀을 너무 크게 하면 FCFS처럼 되고, 너무 작게 하면 컨텍스트 스위칭 오버헤드가 커집니다. 사람이 직접 쓰는 대화형 시스템과, 밤새 대량 연산을 돌리는 배치 시스템은 각각 타임 퀀텀을 크게 두는 게 나을까요, 작게 두는 게 나을까요?

[튜터의 가이드 및 해설]

정답이 하나로 정해진 게 아니라, 그 시스템이 무엇을 중시하느냐에 따라 갈리는 질문이에요. 핵심은 응답시간과 처리량의 트레이드오프입니다.

대화형 시스템 — 작은 타임 퀀텀 — 사람이 키를 누르거나 클릭하면 바로바로 반응해야 합니다. 중시하는 지표는 응답시간이에요. 타임 퀀텀이 작으면 각 작업이 짧게 자주 CPU를 받으니, 모든 작업이 금방 한 번씩 돌아 반응이 빠르게 느껴집니다. 전환 비용이 조금 늘더라도, 사람이 "끊김 없다"고 느끼는 게 더 중요하죠.

배치 시스템 — 큰 타임 퀀텀 — 밤새 대량 연산을 돌리는 시스템은 사람이 실시간으로 보고 있지 않습니다. 중요한 건 전체를 빨리 끝내는 처리량이에요. 타임 퀀텀이 크면 한 작업이 오래 CPU를 쥐고 일하니, 갈아 끼우는 횟수가 줄어 전환 비용이 절약됩니다. 응답이 조금 느려도 상관없으니, 전환 낭비를 줄여 전체 작업을 빨리 끝내는 쪽이 이득이에요.

한 줄로 정리하면 — 타임 퀀텀은 "응답성을 살릴 것인가, 전환 비용을 아낄 것인가"의 손잡이예요. 사람이 기다리는 대화형은 작게(응답 우선), 기계가 알아서 도는 배치는 크게(처리량 우선) 두는 게 일반적입니다. 같은 라운드 로빈이라도 이 손잡이를 어디에 두느냐로 성격이 완전히 달라집니다.

🎯 면접관을 홀리는 핵심 멘트

"타임 퀀텀은 응답시간과 처리량 사이의 손잡이입니다. 사람이 직접 쓰는 대화형 시스템은 응답시간이 중요하니 타임 퀀텀을 작게 둬, 모든 작업이 짧게 자주 돌아 반응이 빠르게 느껴지게 합니다. 전환 비용이 조금 늘어도 끊김 없는 게 더 중요하니까요. 반면 배치 시스템은 처리량이 중요하니 타임 퀀텀을 크게 둬, 갈아 끼우는 횟수를 줄여 전환 낭비를 아끼고 전체를 빨리 끝냅니다. 같은 라운드 로빈이라도 무엇을 중시하느냐에 따라 타임 퀀텀을 반대로 잡는 거죠."


🤔 [생각해볼 주제 3 예시답안] 멀티코어 시대에 동시성 설계가 왜 점점 더 중요해졌을까?

[문제 상황 요약]

예전에는 CPU 하나의 클럭이 해마다 빨라져서, 같은 프로그램도 새 컴퓨터에서 그냥 더 빨라졌습니다. 그런데 발열의 벽 때문에 클럭 높이기가 한계에 부딪히자, 제조사들은 코어 수를 늘리는 쪽으로 방향을 틀었어요. 일을 한 줄로만 처리하는 프로그램은 코어가 8개여도 1개만 쓰고 나머지를 놀립니다.

[튜터의 가이드 및 해설]

하드웨어의 발전 방향이 바뀌면서, 소프트웨어를 짜는 방식에 숙제가 생긴 이야기예요. A-2에서 본 발열의 벽이 출발점입니다.

예전 — 클럭이 알아서 빨라지던 시대 — 한동안은 CPU 하나의 클럭(속도)이 해마다 올라갔습니다. 그래서 프로그래머가 아무것도 안 해도, 같은 프로그램이 새 CPU에서 그냥 더 빨리 돌았어요. "기다리면 빨라진다"가 통하던 시절이죠.

전환점 — 발열의 벽 — 그런데 클럭을 계속 올리니 전력 소모와 발열이 감당이 안 됐습니다(A-2의 발열의 벽). 그래서 제조사들은 클럭을 무작정 높이는 대신, 코어를 여러 개 넣는 쪽으로 방향을 틀었어요. 요즘 CPU가 4코어·8코어·16코어인 이유입니다.

숙제 — 프로그램이 코어를 나눠 써야 한다 — 문제는 여기서 생깁니다. 코어가 8개여도, 프로그램이 일을 한 줄로만(단일 스레드) 처리하도록 짜여 있으면 코어 하나만 쓰고 나머지 7개는 놉니다. 하드웨어가 준 성능을 절반도 못 쓰는 거죠. 코어가 늘어난 만큼 빨라지려면, 프로그램이 일을 여러 갈래로 쪼개(동시성 설계) 여러 코어에 나눠줄 수 있어야 합니다(병렬성 실현). 이제 성능은 "더 빠른 CPU를 기다리는 것"이 아니라 "프로그램을 어떻게 쪼개느냐"에 달리게 됐어요.

그래서 동시성 설계가 중요해졌다 — 멀티코어 시대에는 동시성을 잘 설계하는 능력이 곧 성능입니다. 어떤 일을 병렬로 쪼갤 수 있고 어떤 일은 순서를 지켜야 하는지, 쪼갠 스레드들이 공유 데이터를 안전하게 쓰게 하려면 어떻게 동기화할지 — 이런 고민이 프로그래머의 핵심 역량이 됐어요. 하드웨어가 "코어는 줄 테니 나눠 쓰는 건 너희 몫"이라고 공을 넘긴 셈입니다.

🎯 면접관을 홀리는 핵심 멘트

"예전에는 CPU 클럭이 알아서 빨라져 같은 프로그램도 새 컴퓨터에서 그냥 빨라졌습니다. 그런데 발열의 벽으로 클럭 높이기가 한계에 부딪히자, 제조사들이 코어 수를 늘리는 쪽으로 방향을 틀었어요. 문제는 일을 한 줄로 처리하는 프로그램은 코어가 8개여도 1개만 쓴다는 겁니다. 그래서 이제 성능은 '더 빠른 CPU를 기다리는 것'이 아니라 '일을 여러 갈래로 쪼개 여러 코어에 나눠주는 동시성 설계'에 달리게 됐습니다. 멀티코어 시대에 동시성을 잘 다루는 능력이 곧 성능인 이유입니다."

전체 목록 CS 기초지식

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.