dh_0e

[OS] Thread 본문

Operating System

[OS] Thread

dh_0e 2025. 11. 28. 03:55

Process 복습

  • Process는 시스템에서 다음과 같은 추상화 단위를 나타냄
    • Execution Unit(실행 단위): 스케줄링의 기본 단위
    • Protection Domain(보호 영역): 소유하고 있는 자원에 대한 보호를 제공
  • Program과의 관계
    • 기존의 프로세스 개념은 단일 실행흐름(single thread of control)을 가진 채 실행 중인 프로그램을 의미
    • "만약 1개의 실행 흐름을 여러 개로 만든다면?" 스레드 등장의 배경이 됨 

 

 

Thread

  • 정의
    • Execution Unit: Process 내의 실행 흐름
    • Finer Grain(더 작은 단위): Process보다 더 작은 실행 단위
      • linux에선 light-weight process(LWP)라 명명하고 사용
    • Process가 제공하는 Protection Domain은 없음
      • 스레드들은 프로세스의 보호 영역을 공유
  • 동기
    • 병렬 작업 완수: 하나의 프로세스가 하나의 일만 처리하는 한계를 극복하기 위해, 프로세스 내의 작업을 여러 개로 나누어 각각을 스레드 화하여 병렬적으로 작업을 완수
    • Cooperative Process와의 차이점
      • Cooperative Process(협력 프로세스): 프로세스 간 통신(IPC)이 필요하며, 비용(Context Switching 등)이 많이 듬
      • Thread를 이용한 Cooperation: 프로세스 내에서 협력하는 스레드를 만들면, 프로세스보다 적은 비용으로 협력 프로세스가 하는 일을 동일하게 수행 가능
  • ex) CPU 16코어가 240개의 프로세스, 3300개의 스레드를 맡았을 경우
    • 240개의 주방에서 3300개의 작업(스레드)이 쏟아져 나온다. 16명의 요리사는 특정 주방을 가리지 않고(프로세스가 코어에 배정받는 게 아님), 지금 당장 급한 작업(스레드)을 낚아채서 처리한다. 심지어 한 주방에 요리사 여러 명이 동시에 들어가서 왁자지껄하게 일하기도 하고, 0.01초마다 다른 주방, 다른 작업으로 순간 이동하며 일을 처리한다.

 

Thread & CPU Utilization

  • CPU Utilization 증가: 스레드의 수가 증가할수록 CPU의 활용도(Utilization)가 증가하여 처리량(Throughput)이 향상
  • 임계값 이후 감소: 스레드 수가 임계값을 넘어가면 스레드 전환(Switching) 비용이 증가하여 처리량이 다시 감소함
  • Multi-processor 시스템의 유리함: CPU의 수가 많은 시스템(Multi-processor)일수록, 한 프로세스 내에서 여러 스레드를 병렬적으로 실행하는 것이 유리함

  • 그래프 개요
    • CPU 수가 많을수록 (x>y>z) 전체적인 처리량 곡선이 높게 형성됨
    • 모든 경우에서 스레드 수가 증가하다가 특정 임계값을 지나면 처리량이 감소하는 모양을 보임

 

Process vs Thread

구분 Process (무거운 단위, Heavy Weight) Thread (가벼운 단위, Light Weight)
실행 흐름 하나의 Thread와 같은 실행 흐름 하나의 Process 안에 여러 개의 Thread 존재
메모리 독립성 독립적. 서로 접근하기 어려움 Process의 Code/Data 영역을 공유
Context Switch 비용 큼 (상대적으로 Heavy Weight) 적음 (같은 Memory 영역 사용)

 

 

Thread 구성 요소

  • Thread 고유 정보(Not-Shared)
    • Thread ID: 스레드 식별자
    • Program Counter(PC): 현재 실행 중인 Instruction 주소
    • Register Set: CPU의 레지스터 값들
    • Stack: 지역 변수 및 함수 호출 정보 저장
      • 프로세스의 Stack 영역에 저장되는 것이 아니라 Free Space에 thread를 할당
  • 동일한 Process 내 다른 Thread와 공유하는 것(Shared)
    • Code: Program(Process)의 Code Section
    • Data: Process의 Data Section
    • File: Process에서 Open한 file
[ 높은 주소 (0xFF...)]
+---------------------------+
|      OS Kernel Space      |
+---------------------------+
| [메인 스레드 Stack]       |  <-- '프로세스 스택' (주인공 전용)
|          ↓ (아래로 자람)  |
+---------------------------+
|                           |
|       (빈 공간)           |
|                           |
+---------------------------+
| [스레드 2의 Stack]        |  <-- ★ 여기에 새로 할당! (mmap)
|          ↓                |
+---------------------------+
|       (Guard Page)        |  <-- 서로 침범 못 하게 막는 벽
+---------------------------+
| [스레드 3의 Stack]        |  <-- ★ 여기도 새로 할당!
|          ↓                |
+---------------------------+
|                           |
|      (mmap 영역)          |
|                           |
+---------------------------+
|          ↑ (위로 자람)    |
| [Heap (동적 할당 영역)]   |  <-- malloc() 하면 여기 씀
+---------------------------+
| [Data / BSS]              |  <-- 전역 변수
+---------------------------+
| [Code (Text)]             |  <-- 240명 요리사가 공유하는 레시피
+---------------------------+
[ 낮은 주소 (0x00...)]

 

Single-thread process vs Multi-thread process
Process vs Thread

  • P1, P2는 메모리를 공유하지 않음
  • T1, T2는 메모리를 공유하면서 각자의 스택 영역이 존재
    • 표준 스택 영역(커널 바로 밑의 가장 높은 주소)에 가변적인 메모리를 할당받은 T1이 P3의 메인 스레드
    • T2는 메인 스택(T1) 아래쪽의 빈 공간(Heap과의 사이)에 고정적인 크기를 할당받음

 

Multi-threaded program의 장점

  • Responsiveness(반응성)
    • 프로그램의 특정 스레드가 블록(Block)되거나 시간이 오래 걸리는 작업을 수행하더라도, 다른 스레드들은 계속 실행되고 있기 때문에 사용자 입장에서는 프로그램이 Interactive 하다고 느낄 수 있음
  • Resourcing Sharing(자원 공유)
    • 스레드들은 프로세스의 메모리와 다른 자원들을 공유함
  • Economy(경제성)
    • 스레드는 하나의 프로세스 메모리 영역에서 실행되므로, 새로운 프로세스를 생성하는 것보다 스레드를 생성하는 것이 비용이 적게 듬
  • Scalability(확장성)
    • 여러 개의 스레드가 각각 다른 Processor(CPU 코어)에서 동시에 실행될 수 있음 

 

Multicore Programming

  • 최근 설계 동향
    • 최근 프로세서(코어)는 하나의 칩에 여러 개의 컴퓨팅 코어(Multicore Processor)를 탑재
  • 효율성: 멀티스레드 프로그래밍은 멀티코어 시스템에서 특히 효율적임
    • 운영체제는 각각의 코어를 하나의 프로세서로 인식하고 스케줄링
    • 각각의 스레드에 코어를 할당하여 병렬실행이 가능해짐
  • Cache 공유
    • 멀티코어 시스템은 캐시(Cache)를 공유하기 때문에, 데이터, 코드 등 프로세스의 자원을 공유하는 멀티스레드 프로그래밍에 더욱 효율적임

 

User and Kernel Threads

  • 스레드를 지원하는 주체에 따라 사용자 스레드(User thread)커널 스레드(Kernel thread)로 나뉨

 

User Thread(사용자 수준 스레딩)

특징 설명
구현 주체 Kernel 영역 위, 일반적으로 User Level의 Library를 통해 구현됨 (라이브러리가 생성/스케줄링 관리)
장점 동일 메모리 영역에서 관리되므로 속도가 빠르다.
단점 하나의 스레드가 System Call 요청으로 Block되면, 커널은 이들을 하나의 프로세스로 간주하므로 나머지 모든 스레드 역시 Block된다.

User thread들은 모두 하나의 프로세스로 간주

  • Core 수가 낮을 때 주로 사용

 

Kernel Thread(커널 수준 스레딩)

특징 설명
구현 주체 운영체제(Kernel)에서 스레드 생성, 스케줄링 등을 관리함
장점 1 하나의 스레드가 System Call 호출로 Block되면, 커널은 다른 스레드를 실행시켜 전체적인 Thread Blocking이 없음.
장점 2 Multiprocessor 환경에서 커널이 여러 스레드를 다른 프로세서에 할당할 수 있음.
단점 User Thread보다 생성 및 관리가 느림 (커널 개입 필요).
  • 현대 방식에서 User Thread보다 선호됨

 

Mapping of User & Kernel Thread

1. Many-to-One (다대일)

  • 매핑 방식: 여러 개의 User Level Thread들이 하나의 Kernel Thread로 매핑
  • 주요 관리: Thread 관리는 User level에서 이루어짐
  • 한계점(Concurrency 문제)
    • System Call Blocking: 한 번에 하나의 스레드만 커널에 접근 가능
      하나의 스레드가 커널에 접근(System Call)하면 나머지 스레드들은 대기해야 하므로 진정한 Concurrency를 지원하지 못함
    • Multiprocessor 비효율: 커널 입장에서 여러 스레드는 하나의 프로세스이므로, Multiprocessor 환경에서도 여러 개의 Processor에서 동시에 수행 불가

Many-to-One 예시

2. One-to-One

  • 매핑 방식: 각각의 User Thread를 Kernel Thread로 매핑
    • User Thread가 생성될 때마다 그에 따른 Kernel Thread가 생성됨
  • 장점
    • Many-to-One의 문제였던 System Call 호출 시 다른 thread가 Block 되는 문제를 해결
    • 여러 개의 스레드를 Multiprocessor에서 동시에 수행할 수 있음
  • 한계점
    • Kernel Thread는 한정된 자원이기 때문에, 무한정 생성할 수 없음
      • 생성할 스레드 개수에 대한 고려가 필요

One-to-One 예시

3. Many-to-Many

  • 매핑 방식: 여러 개의 User Thread를 여러 개의 Kernel Thread로 매핑
  • 특징: Kernel Thread는 생성된 User Thread 수와 같거나 적은 수만큼만 생성되어 적절히 Scheduling 됨
  • 장점
    • One-to-One처럼 사용할 스레드 수에 대해 고민할 필요 없음
    • Many-to-One처럼 System Call 사용 시 다른 thread가 Block 되는 현상 없음
    • 커널이 User Thread와 Kernel Thread 사이의 매핑을 적절히 조절하여 위의 장점들을 모두 보장할 수 있음

 

Thread로 인한 OS의 변화

  • 멀티스레드 개념이 도입되면서 기존 프로세스 기반 운영체제System Call(특히 fork(), exec())의 의미에 대한 고려가 필요해짐
  • Process 관련 System Call Semantics는 Process 기준으로 작성됨
    • fork(), exec(): Thread 지원을 위해서는 변화가 필요(더 light 한 fork가 필요)
      • linux의 fork() & clone()
        • fork: 표준적인 프로세스 복제(메모리 독립 프로세스)
          • 실제로는 Copy-on-Write로 처음엔 메모리 페이지를 공유하다가 누가 쓰면 그때 복사
        • clone: 리눅스식 만능 생성기(메모리 공유 옵션 설정으로 프로세스/스레드 둘 다 만들 수 있음)
    • Thread 종료 문제: Multi-thread일 경우, 함께 일하는 thread의 종료는 process보다 복잡해짐 (thread 간의 동기화 복잡 문제)
  • Multi-threaded Programming에 대한 지원
    • 스레드를 도입하면 OS가 프로세스만 관리하던 때와 달리, 실행 단위가 더 잘게 쪼개지면서 커널/런타임이 추가로 책임져야 할 것들이 생김
      • thread 스케줄링
      • thread 간 통신 방법
      • thread가 사용할 메모리 공간의 할당
        • Stack, Thread-specific Data

 

Thread Issues: Creation (fork와 exec)

  • Multithreaded Program에서 fork와 exec의 의미가 달라져야 함
  • fork() System Call의 문제: 하나의 스레드가 fork()를 호출했을 때, 다음 두 가지 중 무엇을 만들 것인지 문제가 발생 
    1. 모든 스레드를 포함한 프로세스 복사
      • 부모 프로세스의 모든 스레드를 복사하여 자식 프로세스를 만듦
    2. fork를 요청한 스레드만 복사
      • fork를 호출한 스레드만을 복사한 프로세스를 만듦
  • exec() System Call과의 관계
    • exec() 호출은 현재 프로세스의 메모리를 새로운 프로그램으로 완전히 교체함
      1. fork 후 exec을 수행할 경우
        • 메모리 낭비: 모든 스레드를 복사한 경우, exec 수행 시 복사된 나머지 스레드들은 불필요하게 복사된 후 교체 
        • fork를 요청한 스레드만이 복사되는 것이 더 바람직함
      2. fork만 수행하고 exec을 수행하지 않는 경우
        • 자식 프로세스가 부모 프로세스의 자원을 이용해야 할 수 있으므로 모든 스레드의 복사가 필요하기도 함
          • fork 후 부모와 자식이 협력하여 계속 일을 해야 한다면, 모든 스레드를 복사하는 것이 맞음
        • 보통 Multithreaded Program에선 exec를 잘 수행하지 않음
    • 운영체제는 이 두 가지 상황을 모두 처리할 수 있도록 fork의 버전을 달리 제공해야 함

Thread Issues: Cancellation

  • Thread Cancellation: Thread의 작업이 끝나기 전에 외부에서 작업을 중지시키는 것
  • 하나의 Thread에서 중지 명령이 결국은 다른 Thread의 모든 작업을 중지시켜야 하는 경우
    • ex) Web Browser에서 Image를 읽어 오는 과정에서 사용자가 Stop 버튼을 눌러 취소할 경우, Image를 읽어오는 Thread 역시 중지가 되어야 함 
  • ★ 자원이 Thread에게 할당된 경우 Cancellation의 문제
    • 스레드가 시스템 자원을 사용하고 있는 상태에서 중지될 경우, 할당된 자원을 함부로 해제할 수 없음
      • 다른 스레드가 그 자원을 사용하고 있을지도 모르기 때문
      • ex) Malloc() cancelled without deallocation

 

Thread Issues: Thread Pools

  • Thread Pool 도입 배경
    • 스레드가 자주 생성되고 제거되는 상황에서, 새로 스레드를 만드는 시간실제 스레드가 동작하는 시간보다 길 수 있음
    • 스레드는 시스템 자원이므로, 시스템 동작을 보장하기 위한 최대한의 스레드 수 제한이 필요
  • Thread Pool 동작 과정
    • Pool 생성: 프로세스가 새로 실행될 때, 정해진 수만큼의 스레드를 미리 만들어 Pool에 할당해 둠
    • 새로운 Thread가 필요하면 Pool에서 가져오고, 작업이 끝나면 그 Thread를 제거하지 않고 다시 Pool에 보관
  • 장점
    • 생성 시간 절약: 스레드 생성에 걸리는 시간을 줄일 수 있음
    • 시스템 부하 감소: 많은 스레드 생성에 따른 시스템 부하를 줄일 수 있음
    • Pool 크기 결정: Pool에서 관리할 Thread의 수는 System의 Memory나 예상되는 작업의 수를 통해 휴리스틱하게 결정
      • 개발자의 역량임

 

Thread Issues: Thread 간 IPC

  • Multithreaded Programming에서는 Process가 아닌 Thread 간 통신이 필요함(ITC)
    • 구현 방식: 동일한 프로세스 내의 Thread 간 통신은, 공유 메모리가 가장 효율적
    • 이유: Thread들은 같은 Process의 Data 영역을 공유하므로, 자연스러운 공유 메모리가 가능해짐
    • 결과: 스레드 간 자원 공유로 인해 IPC가 최소화됨 
  • 다른 Process에 존재하는 Thread와의 통신
    • 일반 Process의 경우 비슷한 성능을 보임
      • ex) Message 전달, Pipe 등
    • 이러한 통신이 빈번하다면, 프로그램 설계의 잘못으로 간주
      • 스레드의 장점(공유 메모리)을 활용하지 못하고 있기 때문
      • 그냥 Multi Process랑 다를 게 없음