스레드와 멀티스레딩
스레드와 멀티스레딩¶
개요¶
스레드(Thread)는 프로세스 내에서 실행되는 경량 실행 단위입니다. 이 레슨에서는 스레드와 프로세스의 차이, 사용자/커널 스레드, 멀티스레딩 모델, 그리고 pthread API를 학습합니다.
목차¶
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: 멀티스레딩 모델¶
다음 설명에 맞는 멀티스레딩 모델을 고르세요.
- 하나의 사용자 스레드가 블로킹되면 전체 프로세스가 블로킹된다.
- Linux NPTL이 사용하는 모델이다.
- 커널 스레드 수보다 많은 사용자 스레드를 효율적으로 관리할 수 있다.
보기: 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 상태 - 계정 정보 - 시그널 핸들러 결과적으로 스레드 컨텍스트 스위치가 프로세스 컨텍스트 스위치보다 빠름.다음 단계¶
- 04_CPU_스케줄링_기초.md - CPU 스케줄링의 기본 개념