스레드와 멀티스레딩

스레드와 멀티스레딩

개요

스레드(Thread)는 프로세스 내에서 실행되는 경량 실행 단위입니다. 이 레슨에서는 스레드와 프로세스의 차이, 사용자/커널 스레드, 멀티스레딩 모델, 그리고 pthread API를 학습합니다.


목차

  1. 스레드란?
  2. 스레드 vs 프로세스
  3. 스레드 제어 블록 (TCB)
  4. 사용자 스레드와 커널 스레드
  5. 멀티스레딩 모델
  6. pthread API 기초
  7. 연습 문제

1. 스레드란?

정의

스레드 (Thread) = 프로세스 내의 실행 흐름
               = CPU 스케줄링의 기본 단위
               = 경량 프로세스 (Lightweight Process)

┌─────────────────────────────────────────────────────────┐
│                    싱글 스레드 프로세스                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │  코드  │  데이터  │       힙       │    스택    │   │
│  └─────────────────────────────────────────────────┘   │
│               하나의 실행 흐름만 존재                    │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                    멀티 스레드 프로세스                   │
│  ┌────────────────────────────────────────────────────┐ │
│  │  코드  │  데이터  │       힙       │               │ │
│  └────────────────────────────────────────────────────┘ │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │  스택1   │  │  스택2   │  │  스택3   │              │
│  │ 스레드1  │  │ 스레드2  │  │ 스레드3  │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│           세 개의 실행 흐름 (병렬 실행 가능)              │
└─────────────────────────────────────────────────────────┘

스레드가 필요한 이유

┌────────────────────────────────────────────────────────┐
│                   스레드 사용 이점                       │
├────────────────────────────────────────────────────────┤
│                                                        │
│ 1. 응답성 (Responsiveness)                             │
│    - 하나의 스레드가 블로킹되어도 다른 스레드 실행        │
│    - UI 스레드와 작업 스레드 분리                       │
│                                                        │
│ 2. 자원 공유 (Resource Sharing)                        │
│    - 같은 주소 공간 공유                               │
│    - IPC 없이 데이터 교환 가능                          │
│                                                        │
│ 3. 경제성 (Economy)                                    │
│    - 프로세스 생성보다 스레드 생성이 빠름                 │
│    - 컨텍스트 스위치 비용 감소                          │
│                                                        │
│ 4. 확장성 (Scalability)                                │
│    - 멀티코어 CPU 활용                                 │
│    - 각 스레드가 다른 코어에서 실행                      │
│                                                        │
└────────────────────────────────────────────────────────┘

2. 스레드 vs 프로세스

공유하는 것과 독립적인 것

┌─────────────────────────────────────────────────────────┐
│                   스레드 간 공유/독립                     │
├────────────────────────┬────────────────────────────────┤
│       공유 (Shared)     │       독립 (Private)           │
├────────────────────────┼────────────────────────────────┤
│ • 코드 섹션            │ • 스레드 ID                     │
│ • 데이터 섹션          │ • 프로그램 카운터 (PC)          │
│ • 힙 영역              │ • 레지스터 집합                  │
│ • 열린 파일            │ • 스택                          │
│ • 신호 처리기          │ • 스케줄링 정보 (우선순위)       │
│ • 현재 작업 디렉토리    │ • 시그널 마스크                  │
│ • 사용자/그룹 ID       │ • errno 값                      │
└────────────────────────┴────────────────────────────────┘

메모리 레이아웃 비교

프로세스 메모리:                멀티스레드 프로세스 메모리:

┌─────────────┐                ┌─────────────────────────┐
│   커널      │                │         커널            │
├─────────────┤                ├─────────────────────────┤
│   스택      │                │ 스레드1 │ 스레드2 │ 스레드3│
│     ↓       │                │  스택   │  스택   │  스택  │
│             │                │   ↓     │   ↓     │   ↓    │
├ ─ ─ ─ ─ ─ ─ ┤                ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│             │                │                         │
│     ↑       │                │           ↑             │
│   힙        │                │         힙 (공유)        │
├─────────────┤                ├─────────────────────────┤
│   데이터    │                │       데이터 (공유)       │
├─────────────┤                ├─────────────────────────┤
│   코드      │                │       코드 (공유)        │
└─────────────┘                └─────────────────────────┘

비용 비교

┌─────────────────────────────────────────────────────────┐
│               프로세스 vs 스레드 비용 비교                │
├───────────────────┬─────────────┬───────────────────────┤
│       항목         │   프로세스   │        스레드         │
├───────────────────┼─────────────┼───────────────────────┤
│ 생성 시간         │    느림     │         빠름          │
│ (Linux 기준)      │  ~10ms      │        ~1ms           │
├───────────────────┼─────────────┼───────────────────────┤
│ 컨텍스트 스위치    │    느림     │         빠름          │
│                   │ TLB 플러시   │ TLB 유지 가능          │
├───────────────────┼─────────────┼───────────────────────┤
│ 메모리 사용       │    많음     │         적음          │
│                   │ 독립된 공간  │  공유 공간            │
├───────────────────┼─────────────┼───────────────────────┤
│ 통신 비용         │    높음     │         낮음          │
│                   │  IPC 필요   │  직접 메모리 접근      │
├───────────────────┼─────────────┼───────────────────────┤
│ 안정성            │    높음     │         낮음          │
│                   │ 격리됨      │  하나가 죽으면 전체 영향│
└───────────────────┴─────────────┴───────────────────────┘

3. 스레드 제어 블록 (TCB)

TCB 구조

┌───────────────────────────────────────────────────────┐
│                   TCB (Thread Control Block)          │
├───────────────────────────────────────────────────────┤
│                                                       │
│  ┌─────────────────────────────────────────────────┐  │
│  │ 스레드 ID (TID)                                 │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ 스레드 상태 (Running, Ready, Blocked...)        │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ 프로그램 카운터 (PC)                            │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ CPU 레지스터 (범용 레지스터, 플래그...)          │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ 스택 포인터                                     │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ 스케줄링 정보 (우선순위)                        │  │
│  ├─────────────────────────────────────────────────┤  │
│  │ 소속 프로세스 PCB 포인터                        │  │
│  └─────────────────────────────────────────────────┘  │
│                                                       │
│  TCB는 PCB보다 훨씬 작음 (스레드 전환이 빠른 이유)     │
│                                                       │
└───────────────────────────────────────────────────────┘

PCB와 TCB 관계

┌─────────────────────────────────────────────────────────┐
│                        PCB                              │
│  ┌───────────────────────────────────────────────────┐  │
│  │ PID, 메모리 정보, 열린 파일, 시그널 핸들러...      │  │
│  │                                                   │  │
│  │  스레드 목록:                                     │  │
│  │  ┌───────────────────────────────────────────┐   │  │
│  │  │     TCB1      │     TCB2      │    TCB3   │   │  │
│  │  │ TID, PC, SP,  │ TID, PC, SP,  │ TID, PC,  │   │  │
│  │  │ 레지스터, 상태 │ 레지스터, 상태 │ SP, ...   │   │  │
│  │  └───────────────────────────────────────────┘   │  │
│  │                                                   │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

4. 사용자 스레드와 커널 스레드

사용자 수준 스레드 (User-Level Threads)

┌─────────────────────────────────────────────────────────┐
│              사용자 수준 스레드 (ULT)                    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   사용자 공간:                                          │
│   ┌───────────────────────────────────────────────────┐ │
│   │              응용 프로그램                         │ │
│   │  ┌─────────────────────────────────────────────┐  │ │
│   │  │  스레드1    스레드2    스레드3               │  │ │
│   │  └─────────────────────────────────────────────┘  │ │
│   │              ↑    ↑    ↑                         │ │
│   │              └────┼────┘                         │ │
│   │                   ↓                              │ │
│   │         스레드 라이브러리                         │ │
│   │         (스케줄링, 생성, 동기화)                  │ │
│   └───────────────────────────────────────────────────┘ │
│                       │                                 │
│   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│                       │                                 │
│   커널 공간:          ↓                                 │
│   ┌───────────────────────────────────────────────────┐ │
│   │              커널 (단일 스레드로 인식)              │ │
│   │                                                   │ │
│   │   커널은 프로세스만 알고, 스레드는 모름             │ │
│   └───────────────────────────────────────────────────┘ │
│                                                         │
│   장점: 빠른 컨텍스트 스위치, 이식성                     │
│   단점: 하나가 블로킹되면 전체 블로킹, 멀티코어 활용 불가 │
│                                                         │
└─────────────────────────────────────────────────────────┘

커널 수준 스레드 (Kernel-Level Threads)

┌─────────────────────────────────────────────────────────┐
│              커널 수준 스레드 (KLT)                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   사용자 공간:                                          │
│   ┌───────────────────────────────────────────────────┐ │
│   │              응용 프로그램                         │ │
│   │  ┌─────────────────────────────────────────────┐  │ │
│   │  │  스레드1    스레드2    스레드3               │  │ │
│   │  └─────────────────────────────────────────────┘  │ │
│   └───────────────────────────────────────────────────┘ │
│              │         │         │                      │
│   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│              ↓         ↓         ↓                      │
│   커널 공간:                                            │
│   ┌───────────────────────────────────────────────────┐ │
│   │              커널 스케줄러                         │ │
│   │  ┌─────────────────────────────────────────────┐  │ │
│   │  │ K-스레드1  K-스레드2  K-스레드3              │  │ │
│   │  └─────────────────────────────────────────────┘  │ │
│   │                                                   │ │
│   │   커널이 각 스레드를 개별적으로 스케줄링           │ │
│   └───────────────────────────────────────────────────┘ │
│                                                         │
│   장점: 멀티코어 활용, 블로킹 시 다른 스레드 실행        │
│   단점: 느린 컨텍스트 스위치 (시스템 콜 필요)            │
│                                                         │
└─────────────────────────────────────────────────────────┘

비교표

┌────────────────────┬────────────────────┬────────────────────┐
│       특성         │    사용자 스레드    │    커널 스레드      │
├────────────────────┼────────────────────┼────────────────────┤
│ 관리 주체          │ 스레드 라이브러리   │ 운영체제 커널       │
├────────────────────┼────────────────────┼────────────────────┤
│ 생성/전환 속도     │ 빠름 (시스템콜 X)  │ 느림 (시스템콜 O)   │
├────────────────────┼────────────────────┼────────────────────┤
│ 멀티코어 활용      │ 불가능             │ 가능               │
├────────────────────┼────────────────────┼────────────────────┤
│ 블로킹 시스템콜    │ 전체 프로세스 블록  │ 해당 스레드만 블록  │
├────────────────────┼────────────────────┼────────────────────┤
│ OS 지원 필요       │ 불필요             │ 필요               │
├────────────────────┼────────────────────┼────────────────────┤
│ 예시               │ GNU Portable Threads│ Linux NPTL, Windows│
└────────────────────┴────────────────────┴────────────────────┘

5. 멀티스레딩 모델

다대일 모델 (Many-to-One, N:1)

┌─────────────────────────────────────────────────────────┐
│                  다대일 모델 (N:1)                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   사용자 공간:                                          │
│   ┌─────────────────────────────────────────────────┐   │
│   │  ULT1    ULT2    ULT3    ULT4    ULT5          │   │
│   │   │       │       │       │       │            │   │
│   │   └───────┼───────┼───────┼───────┘            │   │
│   │           └───────┼───────┘                    │   │
│   │                   ↓                            │   │
│   │           스레드 라이브러리                     │   │
│   └─────────────────────────────────────────────────┘   │
│                       │                                 │
│   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│                       ↓                                 │
│   커널 공간:    ┌──────────┐                            │
│                 │  KLT 1   │                            │
│                 └──────────┘                            │
│                                                         │
│   • 장점: 효율적인 스레드 관리                          │
│   • 단점: 멀티코어 활용 불가, 하나 블로킹 시 전체 블로킹  │
│   • 예시: 초기 Green Threads                            │
│                                                         │
└─────────────────────────────────────────────────────────┘

일대일 모델 (One-to-One, 1:1)

┌─────────────────────────────────────────────────────────┐
│                  일대일 모델 (1:1)                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   사용자 공간:                                          │
│   ┌─────────────────────────────────────────────────┐   │
│   │  ULT1    ULT2    ULT3    ULT4                  │   │
│   │   │       │       │       │                    │   │
│   └───┼───────┼───────┼───────┼────────────────────┘   │
│       │       │       │       │                        │
│   ━━━━│━━━━━━━│━━━━━━━│━━━━━━━│━━━━━━━━━━━━━━━━━━━━━━━ │
│       ↓       ↓       ↓       ↓                        │
│   커널 공간:                                            │
│   ┌───────────────────────────────────────────────┐     │
│   │  KLT1    KLT2    KLT3    KLT4                │     │
│   └───────────────────────────────────────────────┘     │
│                                                         │
│   • 장점: 멀티코어 활용, 블로킹 시 다른 스레드 계속 실행  │
│   • 단점: 커널 스레드 생성 오버헤드                      │
│   • 예시: Linux NPTL, Windows, macOS                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

다대다 모델 (Many-to-Many, M:N)

┌─────────────────────────────────────────────────────────┐
                  다대다 모델 (M:N)                       
├─────────────────────────────────────────────────────────┤
                                                         
   사용자 공간:                                          
   ┌─────────────────────────────────────────────────┐   
     ULT1  ULT2  ULT3  ULT4  ULT5  ULT6  ULT7        
                                              
      └─────┼─────┼─────┼─────┼─────┼─────┘          
            └─────┼─────┼─────┼─────┘                
                                                  
              스레드 스케줄러                         
   └─────────────────────────────────────────────────┘   
                                                     
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 
                                                     
   커널 공간:                                            
   ┌───────────────────────────────────────────────┐     
         KLT1      KLT2      KLT3                     
   └───────────────────────────────────────────────┘     
                                                         
   7개의 사용자 스레드가 3개의 커널 스레드에 매핑          
                                                         
    장점: 유연성, 효율성과 병렬성의 균형                 
    단점: 구현 복잡성                                   
    예시: Solaris, Go goroutines                       
                                                         
└─────────────────────────────────────────────────────────┘

모델 비교

┌──────────────┬──────────────┬──────────────┬──────────────┐
│     특성      │   N:1       │    1:1      │    M:N      │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 병렬 실행     │ 불가능       │ 가능         │ 가능         │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 블로킹 문제   │ 전체 블로킹   │ 영향 없음    │ 부분적 영향  │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 스레드 생성   │ 빠름         │ 느림         │ 중간         │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 구현 복잡도   │ 낮음         │ 낮음         │ 높음         │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 스케일러빌리티│ 낮음         │ 높음         │ 높음         │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ 현대 OS      │ 거의 사용 X   │ 주로 사용    │ 일부 사용    │
└──────────────┴──────────────┴──────────────┴──────────────┘

6. pthread API 기초

스레드 생성과 종료

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 스레드가 실행할 함수
void* thread_function(void* arg) {
    int thread_num = *(int*)arg;
    printf("스레드 %d 시작\n", thread_num);

    // 작업 수행
    sleep(1);

    printf("스레드 %d 종료\n", thread_num);
    return NULL;
}

int main() {
    pthread_t threads[3];
    int thread_args[3] = {1, 2, 3};

    // 스레드 생성
    for (int i = 0; i < 3; i++) {
        int result = pthread_create(&threads[i], NULL,
                                    thread_function, &thread_args[i]);
        if (result != 0) {
            perror("pthread_create failed");
            exit(1);
        }
    }

    // 모든 스레드가 종료될 때까지 대기
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
        printf("스레드 %d join 완료\n", thread_args[i]);
    }

    printf("메인 스레드 종료\n");
    return 0;
}

/*
컴파일: gcc -pthread thread_example.c -o thread_example

출력 (순서는 달라질 수 있음):
스레드 1 시작
스레드 2 시작
스레드 3 시작
스레드 1 종료
스레드 2 종료
스레드 3 종료
스레드 1 join 완료
스레드 2 join 완료
스레드 3 join 완료
메인 스레드 종료
*/

pthread API 주요 함수

┌────────────────────────┬────────────────────────────────────┐
        함수                           설명                  
├────────────────────────┼────────────────────────────────────┤
 pthread_create()         스레드 생성                      
 pthread_join()          스레드 종료 대기                    
 pthread_exit()          현재 스레드 종료                    
 pthread_self()          현재 스레드 ID 반환                 
 pthread_equal()          스레드 ID 비교                   
 pthread_detach()        스레드 분리 (자동 자원 회수)         
 pthread_cancel()        다른 스레드 취소 요청               
├────────────────────────┼────────────────────────────────────┤
 pthread_mutex_init()    뮤텍스 초기화                       
 pthread_mutex_lock()    뮤텍스 잠금                         
 pthread_mutex_unlock()  뮤텍스 해제                         
 pthread_mutex_destroy() 뮤텍스 파괴                         
├────────────────────────┼────────────────────────────────────┤
 pthread_cond_init()     조건 변수 초기화                    
 pthread_cond_wait()     조건 변수 대기                      
 pthread_cond_signal()   조건 변수 시그널                    
 pthread_cond_broadcast() 모든 대기 스레드에 시그널           
└────────────────────────┴────────────────────────────────────┘

반환 값 받기

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 정수의 제곱을 계산하고 반환
void* calculate_square(void* arg) {
    int num = *(int*)arg;
    int* result = malloc(sizeof(int));
    *result = num * num;

    printf("스레드: %d의 제곱 = %d\n", num, *result);

    return (void*)result;  // 힙에 할당된 결과 반환
}

int main() {
    pthread_t thread;
    int num = 5;
    void* result;

    // 스레드 생성
    pthread_create(&thread, NULL, calculate_square, &num);

    // 스레드 종료 대기 및 결과 받기
    pthread_join(thread, &result);

    printf("메인: 결과 = %d\n", *(int*)result);

    free(result);  // 메모리 해제

    return 0;
}

/*
출력:
스레드: 5의 제곱 = 25
메인: 결과 = 25
*/

스레드 분리 (Detach)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* detached_thread(void* arg) {
    printf("분리된 스레드 시작\n");
    sleep(2);
    printf("분리된 스레드 종료\n");
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_attr_t attr;

    // 스레드 속성 초기화
    pthread_attr_init(&attr);

    // 분리 상태로 설정
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 분리된 스레드 생성
    pthread_create(&thread, &attr, detached_thread, NULL);

    // 속성 객체 파괴
    pthread_attr_destroy(&attr);

    printf("메인 스레드: 분리된 스레드를 join하지 않아도 됨\n");

    // 분리된 스레드가 실행할 시간 확보
    sleep(3);

    printf("메인 스레드 종료\n");
    return 0;
}

/*
분리된 스레드:
- 종료 시 자동으로 자원 회수
- pthread_join() 불필요 (오히려 에러)
- 백그라운드 작업에 적합
*/

스레드 안전 문제 예시

#include <stdio.h>
#include <pthread.h>

int counter = 0;  // 공유 변수

void* increment(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        counter++;  // 경쟁 상태 (Race Condition)!
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 예상: 2000000, 실제: 그보다 작은 값 (매번 다름)
    printf("Counter: %d\n", counter);

    return 0;
}

/*
counter++ 연산은 원자적이지 않음:
1. 메모리에서 counter 값 읽기
2. 값 증가
3. 메모리에 결과 저장

두 스레드가 동시에 실행하면 값이 손실됨 (다음 레슨에서 해결)
*/

7. 연습 문제

문제 1: 스레드와 프로세스

다음 중 스레드가 공유하지 않는 것을 모두 고르세요.

A. 코드 섹션 B. 데이터 섹션 C. 스택 D. 힙 E. 프로그램 카운터 F. 열린 파일

정답 보기 **C, E** - 스택: 각 스레드는 자신만의 스택을 가짐 (지역 변수, 함수 호출 정보) - 프로그램 카운터: 각 스레드는 다른 위치의 코드를 실행할 수 있음 나머지는 모두 스레드 간 공유됨.

문제 2: 멀티스레딩 모델

다음 설명에 맞는 멀티스레딩 모델을 고르세요.

  1. 하나의 사용자 스레드가 블로킹되면 전체 프로세스가 블로킹된다.
  2. Linux NPTL이 사용하는 모델이다.
  3. 커널 스레드 수보다 많은 사용자 스레드를 효율적으로 관리할 수 있다.

보기: N:1, 1:1, M:N

정답 보기 1. **N:1 (다대일)** - 모든 사용자 스레드가 하나의 커널 스레드에 매핑되므로, 하나가 블로킹되면 전체가 블로킹됨 2. **1:1 (일대일)** - Linux NPTL, Windows, macOS가 사용하는 모델 3. **M:N (다대다)** - 여러 사용자 스레드를 적은 수의 커널 스레드에 매핑하여 효율적으로 관리

문제 3: pthread 코드 분석

다음 코드의 출력 결과로 가능한 것을 모두 고르세요.

#include <stdio.h>
#include <pthread.h>

void* print_msg(void* arg) {
    char* msg = (char*)arg;
    printf("%s", msg);
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, print_msg, "A");
    pthread_create(&t2, NULL, print_msg, "B");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("C");
    return 0;
}

A. ABC B. BAC C. CAB D. ACB

정답 보기 **A, B** - A와 B의 출력 순서는 스레드 스케줄링에 따라 달라질 수 있음 - C는 항상 마지막 (두 스레드가 모두 join된 후 출력) - 따라서 가능한 출력: ABC 또는 BAC CAB, ACB는 불가능 (C는 join 이후에만 출력됨)

문제 4: 스레드 생성

다음 코드의 문제점을 설명하세요.

#include <stdio.h>
#include <pthread.h>

void* thread_func(void* arg) {
    int* num = (int*)arg;
    printf("Thread: %d\n", *num);
    return NULL;
}

int main() {
    pthread_t threads[5];

    for (int i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, thread_func, &i);
    }

    for (int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}
정답 보기 **문제: 경쟁 상태 (Race Condition)** 모든 스레드가 같은 변수 `i`의 주소를 전달받음. 스레드가 실행될 때 `i`의 값이 이미 변경되어 있을 수 있음. 예상 출력: 0, 1, 2, 3, 4 실제 가능한 출력: 5, 5, 5, 5, 5 또는 2, 3, 4, 5, 5 등 **해결 방법:**
int thread_args[5];

for (int i = 0; i < 5; i++) {
    thread_args[i] = i;
    pthread_create(&threads[i], NULL, thread_func, &thread_args[i]);
}
각 스레드에 별도의 인자 공간을 할당해야 함.

문제 5: TCB와 PCB

TCB가 PCB보다 작은 이유를 스레드의 특성과 연관지어 설명하세요.

정답 보기 TCB가 PCB보다 작은 이유: 1. **자원 공유**: 스레드는 코드, 데이터, 힙, 열린 파일 등을 같은 프로세스의 다른 스레드와 공유함. 이 정보는 PCB에만 저장되고 TCB에는 저장할 필요가 없음. 2. **TCB에 저장되는 정보가 적음**: - 스레드 ID - 프로그램 카운터 - 레지스터 집합 - 스택 포인터 - 스레드 상태 - 우선순위 3. **PCB에 추가로 저장되는 정보**: - 메모리 관리 정보 (페이지 테이블) - 열린 파일 목록 - I/O 상태 - 계정 정보 - 시그널 핸들러 결과적으로 스레드 컨텍스트 스위치가 프로세스 컨텍스트 스위치보다 빠름.

다음 단계


참고 자료

to navigate between lessons