C, C++/뇌를 자극하는 윈도우즈 시스템 프로그래밍

[뇌를 자극하는 윈도우즈 시스템 프로그래밍] 11장 쓰레드의 이해

Basaeng 2025. 6. 10. 16:26

개요

하나의 프로그램 내에서도 여러가지의 실행 흐름이 필요할 때가 많다.

특히 복잡한 프로그램 (예를 들어 게임)에는 프로그램 내에서 실행 흐름 다양화를 위해 전통적으로 프로세스를 여러개 생성하는 방식을 사용했다.

 

하지만 프로세스는 각각 별개의 컨텍스트를 가지고 있고, 이를 전환하는 컨텍스트 스위칭은 실행 비용이 크다.

따라서 이러한 컨텍스트 스위칭의 비용을 줄이면서도, 실행 흐름을 다양화하기 위해, 컨텍스트의 일부는 공유하고, 일부는 별도로 가지고 있는 쓰레드의 개념이 생겼다. 

 

쓰레드는 하나의 프로그램 내에서 별개의 실행흐름이지만, 프로세스와 달리 완전한 독립 구조가 아니다.

하나의 프로세스의 쓰레드들은 공유하는 요소들이 존재한다. 따라서 컨텍스트 스위칭 비용이 프로세스보다 적다.

 

Thread의 특징

thread는 메모리 영역 중 stack을 제외한 메모리 영역(heap, data 등)을 공유한다. 따라서 아래와 같은 특징을 가진다.

1. stack과 register가 독립적이기 때문에 개별 실행 흐름을 가질 수 있다.

2. code 영역을 공유하기 때문에 thread가 생성된다면 지정된 시작 루틴부터 process의 code를 실행한다.

3. data, heap 영역을 공유하기 때문에 thread는 서로 통신이 간편하다. 다만 동기화 측면에서 문제가 생길 수 있다.

 

Windows에서는 기본적으로 thread가 존재하며 이를 활용해서 스케줄링한다.


Thread 구현 모델에 따른 구분

Kernel Level Thread & User Level Thread

Thread는 어떻게 만들어질까?

 

1. Thread는 Kernel이 만들 수 있다.

OS가 제공하는 System Call을 사용해 Thread를 만들 수 있다.

Thread를 생성하고, 스케줄링하는 주체가 Kernel이라면, Kernel Level Thread라고 한다.

 

2. Thread는 User Level에서 만들 수도 있다.

과거에는 OS가 thread를 지원하지 않는 경우도 있었다. 이럴 때 user level에서 thread를 생성하는 방법을 제공하기 때문에

User Level Thread라고 한다.

 

32비트 OS는 하나의 프로세스에 4GB의 가상 메모리가 할당된다.

이 중 2GB는 User영역, 나머지 2GB는 Kernel영역이다.

 

현재 64비트 운영체제 OS는 하나의 프로세스에 16TB(현재 실제로는 훨씬 큰 크기의 메모리를 사용하지만 책의 내용을 기반으로 작성)의 가상메모리가 할당된다.

이 중 8TB는 User영역, 나머지 8TB는 Kernel 영역이다. 

 

User영역은 각 프로세스마다 개별적으로 실제 물리메모리에 매핑된다.

Kernel 영역은 모든 프로세스에 동일한 가상주소 범위로 매핑되며, 그 가상주소들은 실제 동일한 물리 메모리(공통된 커널 코드/데이터)에 연결된다.

 

Kernel Mode & User Mode

위에서 설명한 Kernel 영역, User영역은 메모리 상에서의 위치의 차이이다.

User영역에는 프로그래머가 작성한 프로그램이 동작하는 메모리이고, Kernel 영역에는 OS 프로그램이 동작하는 메모리이다.

 

위에서 설명했듯이 User영역은 프로세스마다 각각 사용해 문제가 생기면 해당 프로세스에만 문제가 생기지만,

Kernel 영역은 모든 프로세스가 공유하기 때문에 문제가 생기면 치명적이다.

 

따라서 Kernel 영역의 안전성을 위해 Kernel Mode와 User Mode를 구분해 사용한다.

일반적인 실행흐름에서는 User Mode이며, Kernel 동작이 필요한 경우에 System Call을통해 Kernel Mode로 전환해 Kernel 메모리에 접근하는 방식이다.

 

OS는 일반적인 실행흐름에서 Kernel 영역에 접근하려고 하면, 접근하지 못하도록 막고있다. 

 

Kernel이 필요한 함수 예를 들어 입출력 등은 Mode의 전환이 필요한데, Mode의 전환은 시스템에 부담이된다.

따라서 잦은 전환은 성능에 영향을 줄 수 있다.

 

 

Kernel Level Thread & User Level Thread의 장점, 단점

Kernel Level Thread

장점:

  • OS 커널이 직접 관리하므로, 여러 CPU 코어에서 진짜 병렬 실행이 가능함
  • 스레드가 Block되어도 다른 스레드는 계속 실행 가능 (독립적 스케줄링)
  • OS 수준의 스케줄러, 우선순위, 동기화 기능을 그대로 활용 가능

 

단점: 

 

  • 시스템 콜을 통해 스레드를 생성, 스케줄링해야 하므로, 모드 전환 비용이 큼
  • 스레드 생성/전환/동기화가 느림
  • 커널 자원을 더 많이 소비함 (관리 복잡도 증가)

 

 

User Level Thread

장점: 

 

  • 스레드 생성/스위칭/스케줄링이 매우 빠름 (커널 개입이 없음)
  • 시스템 콜을 사용하지 않으므로 모드 전환 오버헤드 없음
  • 자원 소모가 적고, 스레드 라이브러리 수준에서 커스터마이징 가능

 

단점: 

 

  • OS는 스레드 단위가 아닌 프로세스 단위로만 인식
  • 따라서 하나의 ULT가 Block되면 전체 프로세스가 Block된 것처럼 보임
  • 다중 CPU 코어에서 병렬 실행 불가 (커널은 하나의 실행 흐름만 알고 있음)
  • 타이머 기반 선점(preemption)이 어려움 → 명시적 yield() 필요