| 4.2 스레드 DEEP DIVE |
| 4.2.1 스레드 주소 공간 |
| 4.2.2 스레드 Life cycle |
| 4.2.3 스레드 Operation |
| 4.2.4 스레드 Context |
이번 시간에는 스레드에 대해 조금 더 깊게 알아볼 것이다.
4.2.1 스레드 주소 공간
스레드가 생성되고 실행되는 동안 접근 가능한 메모리 영역이 스레드 주소공간이다. 스레드 주소공간은 프로세스의 주소 공간 내에 형성된다. 일반적인 함수가 수직적인 관계라면 스레드는 아래와 같이 side-by-side 형태이다.

스레드 사적공간은 스레드 코드, 스레드 로컬 스토리지, 스레드 스택으로 구분 된다.
스레드들이 공유하는 공간은 프로세스의 코드, 프로세스의 데이터 공간 (로컬 스토리지 제외), 프로세스의 힙 영역이다.
#include <pthread.h> // pthread lib
#include <stdio.h>
#include <stdlib.h>
int gsum = 0;
int __thread tsum = 1;
void func(int a) {
printf("5_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
int b = a + 10;
gsum += b;
tsum += b;
printf("6_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
}
void* myThread(void *p) {
int a = (*(int*)p);
printf("2_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
for(int i = 0; i < 30000000/a; i++);
gsum += a;
tsum += a;
printf("3_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
func(a);
printf("7_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
}
int main() {
pthread_t tid[2];
int arg[2] = {1000, 3000} ;
printf("1_main. gsum= %d / tsum= %d \n", gsum, tsum);
pthread_create(&tid[0], NULL, myThread, &arg[0]);
pthread_create(&tid[1], NULL, myThread, &arg[1]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
printf("8_main. gsum= %d / tsum= %d \n", gsum, tsum);
return 0;
}
다음과 같은 결과가 나온다.


4.2.2 스레드 Life cycle
스레드의 라이프 사이클은 프로세스와 매우 흡사하다. TCB로 관리한다.
스레드의 상태 변화
- 준비 상태 (Ready) : 스레드가 스케줄 되기를 기다리는 상태
- 실행 상태 (Running) : 스레드가 CPU에 의해 실행중인 상태
- 대기 상태 (Blocked) : 스레드가 입출력을 요청하거나 sleep()과 같은 시스템 호출로 인해 커널에 의해 중단된 상태
- 종료 상태 (Terminated) : 스레드가 종료한 상태

4.2.3 스레드 Operation
스레드 생성
스레드는 스레드를 생성하는 시스템 호출이나 라이브러리 함수를 호출하여 다른 스레드를 생성할 수 있다. 또한 프로세스가 생성되면 자동으로 main 스레드가 생성된다.
스레드 종료
프로세스의 종료와 스레드의 종료에는 구분이 필요하다.
- 프로세스 종료
- 프로세스에 속한 어떤 스레드라도 exit() 시스템 호출을 부르면 프로세스는 종료된다. 즉, 모든 스레드는 종료된다.
- 메인 스레드가 종료될 시, 모든 스레드가 종료된다.
- 모든 스레드가 종료되면 프로세스 또한 종료된다.
- 스레드 종료
- pthread_exit()과 같이 스레드만 종료하는 함수 호출 시, 해당 스레드만 종료된다.
- main() 함수에서 pthread_exit()을 부르면 역시 main 스레드만 종료된다.
스레드 조인
스레드가 다른 스레드가 종료될 때 까지 대기하는 것이다. 주로 부모 스레드가 자식 스레드의 종료를 대기할 때 쓰인다.

스레드 양보
스레드가 자발적으로 yield()와 같은 함수 호출을 통해 자신의 실행을 중단하고 다른 스레드를 스케줄하도록 지시하는 것이다.
4.2.4 스레드 Context
스레드의 실행중인 상태정보는 TCB에 저장된다. TCB, Thread control block은 스레드가 생성될 때 만들어지며, 스레드가 소멸되면 함께 사라진다. 각종 CPU 레지스터 값을 관리한다. PC, SP/BP, Flag 등과 같은 값 들이다. 나머지 메모리는 공유하므로 레지스터들만 저장해두었다가 필요할 때 CPU에 복귀할 시 이전에 실행하던 상태로 돌아갈 수 있다.
Thread context switching
현재 실행중인 스레드를 중단시키고 다른 스레드에게 CPU를 할당할 경우, 현재 CPU context를 TCB에 저장하고 다른 TCB에 저장된 context를 CPU에 적재하는 것이다.
1. CPU 레지스터 저장 및 복귀
- 현재 실행중인 스레드 A의 컨텍스트를 TCB - A에 저장한다.
- TCB - B에 저장된 스레드 B의 컨텍스트를 CPU에 적재한다.
- CPU는 스레드 B가 이전에 중단된 위치에서 실행 재개할 수 있다.
- SP 레지스터를 복귀함으로서 자신의 이전 스택을 되찾게 한다.
- 스택에는 이전 중단될 때 실행하던 함수의 매개변수나 지역변수들이 그대로 저장되어있다.
2. 커널 정보 수정
- TCB - A와 TCB - B에 스레드 상태 정보와 CPU 사용 시간 등을 수정한다.
- TCB - A를 준비 리스트나 블록 리스트로 옮긴다.
- TCB - B를 준비 리스트에서 복귀한다.
Overhead in context switching
Context switching은 상당히 비싼 연산이다. 따라서 잦은 경우 컴퓨터 처리율이 심각하게 저하될 수 있다.
동일한 프로세스의 다른 스레드로 스위칭 되는 경우
- 컨텍스트 저장 및 복귀 : 현재 스레드의 컨텍스트를 TCB에 저장, TCB로부터 스레드 컨텍스트를 CPU에 복귀
- TCB 리스트 조작
- 캐쉬 Flush와 채우기 시간
다른 프로세스의 스레드로 스위칭하는 경우
다른 프로세스로 교체되면, CPU가 실행하는 주소 공간이 바뀌는 큰 변화로 인한 추가적인 오버헤드가 발생한다.
- 추가적인 메모리 오버헤드 : 시스템 내에 현재 실행중인 프로세스의 매핑 테이블을 새로운 프로세스의 매핑 테이블로 교체
- 추가적인 캐시 오버헤드 : 프로세스가 바뀌기 때문에, CPU 캐시에 담긴 코드와 데이터 무력화. 새 프로세스의 쓰레드가 실행을 시작하면 CPU 캐시 미스 발생. 캐시가 채워지는데 상당한 시간을 소요한다.
'CS > 운영 체제' 카테고리의 다른 글
| 4.4 스레드의 추가적 이슈들 (3) | 2024.04.20 |
|---|---|
| 4.3 스레드 모델 (3) | 2024.04.20 |
| 4.1 스레드 개요 (1) | 2024.04.20 |
| 4.0 스레드 (1) | 2024.04.20 |
| 3.4 프로세스 계층 구조 (2) | 2024.04.20 |