운영체제 4장 - 스레드
목차
4.1 개요
4.2 멀티코어 프로그래밍
4.3 다중 스레드 모델
4.4 Thread 라이브러리
4.5 묵시적 쓰레딩
4.6 스레드 관련 이슈
4.1 개요
- Thread : CPU의 기본 실행 단위
- 단일 스레드 프로세스 : 전통적인 프로세스. 한개의 실행 단위로 구성됨
- 다중 스레드 프로세스 :
- 여러개의 실행 스레드를 갖는 프로세스
- 한 프로세스가 동시에 하나 이상의 작업 수행 가능
- 전통적인 프로세스의 확장
- 스레드의 사용 자원
1
2
3
4
5
6
7
- 같은 프로세서에 속한 다른 스레드들과 일부 자원 공유
- code, data, OS 자원(open file, signal)들을 공유함
- stack, CPU register 저장 공간등은 thread 전용 공간 사용
- Thread Control Block(TCB)
- 스레드에 대한 정보를 보관
- 프로세스의 PCB와 유사
- Thread ID, 실행 상태, PC, thread context, stack, thread 메모리 공간 등 - 경량 프로세스 : thread 생성에 필요한 자원이 process 생성에 필요한 자원보다 작음 - 중량 프로세스 : 반대로 thread 가 process 생성보다 더 무거움
Thread 사용 이유
- 단일 응용 프로그램이 여러개의 작업을 동시에 실행할 필요성을 느낌
- 다중 프로세스는 프로세스 생성 오버헤드 발생
- 스레드 생성에 필요한 자원이 적으므로, 다중 스레드 프로세스가 효율적
- 장점
- 응답성(Responsiveness)
- 사용자에 대한 응답성 증가
- 프로그램 일부가 Block 되거나, 긴 작업을 수행해도 대화형 작업의 계속 진행을 허용
- 자원 공유(Resource Sharing)
- 기본적으로 프로세스의 메모리와 자원을 공유함
- 같은 주소 공간에서 여러개의 다른 작업을 수행하는 스레드 허용
- 커널 서비스 호출 없이 스레드간의 통신이 쉬움
- 경제성(Economy)
- 스레드 생성, 종료, Context Switch에 소요되는 오버헤드가 프로세스에 비해 적음
- 규모 확장성(Scalability), 적응성
- 다중 프로세서 구조에서 스레드들을 병렬 처리 가능
- 규모가 커질수록 성능이 높아짐
- 응답성(Responsiveness)
4.2 멀티코어 프로그래밍
- 단일 코어 시스템에서의 병행(concurrent) 실행
- T1, T2, T3, T4 가 빠르게 번갈아가면서 실행됨
- 멀티코어 시스템에서의 병렬(parallel) 실행
- 코어1과 코어2가 {T1, T3}, {T2, T4}를 각각 나누어 실행
- 병렬(Parallel)과 병행(Concurrency)
- 병렬 시스템 : 동시에 1개 이상의 작업을 수행
- 병행 시스템 : 동시실행 불가. 1개 이상의 작업을 진행하면서, 각각 하나씩 처리
- 암달의 법칙(Amdahl’s Law)
- N개의 프로세서를 사용하여 얻을 수 있는 가능한 성능 이득을 정리한 공식
- 컴퓨터 시스템의 일부를 개선할 때 전체적으로 얼마만큼의 최대성능이 향상되는지 계산할때 사용함
- 병렬 실행의 유형
- data parallel : 데이터 부분집합에 대해서 동일한 연산 수행
- task parallel : 각 프로세서마다 고유한 연산 수행
4.3 다중 스레드 모델
- 사용자 스레드와 커널 스레드
- User Threads : 커널 지원 없이 사용자 수준에서 thread library에 의해 지원됨
- Kernel Threads : OS 커널에서 직접 지원되고 관리됨
- 모든 현대 운영체제는 Kernel THreads를 지원함
- 사용자 프로그램 스레드와 커널 스레드간에 연관 관계가 존재함
1. Many-to-One 모델
- 다수의 사용자 스레드 - 하나의 커널 스레드 관계
- 스레드 스케줄링과 동기화가 사용자 공간의 스레드 라이브러리에서 수행됨
- 장점 : 문맥교환이 적고, 동기화 오버헤드가 작아서 효율적
- 단점 :
- 한 스레드가 Blocking System Call을 호출하여 Block되면, 전체 프로세스가 블럭됨
- 커널이 user-level 스레드의 존재를 알지 못함
- 멀티프로세서 시스템에서 스레드들의 병렬 실행 불가
- 한 스레드가 Blocking System Call을 호출하여 Block되면, 전체 프로세스가 블럭됨
- Solaris Green Threads, GNU Portable Thread 등
2. One-to-One 모델
- 각 사용자 스레드가 한개의 커널 스레드에 연관됨
- 장점 :
- 더 많은 병행, 병렬 실행 가능
- 한 스레드가 blocking system call을 호출하여 블록되어도, 커널은 같은 프로세스의 다른 thread로 스케줄링
- 더 많은 병행, 병렬 실행 가능
- 단점 :
- 스레드 생성 및 문맥교환 오버헤드
- 커널 시스템 호출을 사용하여 커널 스레드를 생성해야함
- 커널에서 thread 문맥교환이 이루어짐
- 최대 thread 수에 제한
- 스레드 생성 및 문맥교환 오버헤드
- windows, linux, solaris 9 이후 버전
3. Many-to-Many 모델
- 다수의 사용자 레벨 스레드가 다수의 커널 스레드에 연관
- 커널 스레드의 수는 사용자 레벨 스레드의 수와 작거나 같음
- 커널 스레드들을 사용자 레벨 스레드들이 Multiplex하여 사용함
- 사용자 라이브러리가 커널 스레드를 할당시켜줌
- 커널 스레드 개수는 응용프로그램이나 기계에 따라 결정됨
- 장점 :
- one-to-one 모델과 같은 병행/병렬성
- 필요한 만큼의 커널 스레드와 연관되는 경우
- Blocking System Call을 호출하여 블록되어도, 다른 thread를 스케줄
- 필요한 만큼의 커널 스레드와 연관되는 경우
- 사용자 스레드 개수보다 적은 커널 스레드를 사용할 수 있어, 커널 스레드 수의 제한에 영향이 없
- one-to-one 모델과 같은 병행/병렬성
4. Two-Level 모델
- many-to-many 모델의 변형 모델
- 일부 사용제 스레드에 대해 one-to-one 연관 허용
- IRIX, HP-UX, Tru64 UNIX, Solaris 8
4.4 Thread 라이브러리
- Thread를 생성하고 관리하기 위한 API 제공
- Thread 라이브러리 구현
- user-level library : 완전히 user space 에서 구현
- library 함수 호출은 user space에서의 함수 호출로 이어짐
- kernel-level library : 라이브러리의 코드와 데이터가 kernel space에 존재
- libary 함수 호출은 커널에 대한 system call 호출로 이어짐
- user-level library : 완전히 user space 에서 구현
- 주로 사용되는 3가지 Thread Libaray
- POSIX Pthread : user 혹은 kernel level 라이브러리
- Windows thread : kernel level 라이브러리
- Java Thread : Java thread API
- 호스트 시스템에서 사용 가능한 thread library로 구현
Pthreads
- UNIX
- Thread 생성 및 동기화를 위한 POSIX 표준 API (IEEE 1003.1c)
- 스레드 라이브러리의 동작에 대한 명세
- UNIX 계열 OS에서 일반적으로 제공됨(Solaris, Linux, Mac OS X)
- 주요 함수들
pthread_create()
: 스레드 생성pthread_join()
: 스레드 종료를 기다림pthread_exit()
: 스레드 종료pthread_attr_init()
: 스레드 attribute를 default 값으로 초기화- 등등
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <pthread.h>
#include <stdio.h>
int sum; // 스레드 공유 자원
void *runner(void *param); // 스레드
int main(int argc, char *argv[]){
pthread_t tid; // 스레드 식별자
pthread_attr_t attr; // 스레드 attribute 설정
// 기본 attribute 설정
pthread_attr_init(&attr);
// 스레드 생성
ptrehad_create(&tid, &attr, runner, argv[1]);
// 스레드 종료를 기다림
pthread_join(tid, NULL);
printf("sum = %d\n", sum);
}
// 간단히 sum에 i부터 쭉 더하는 스레드
void *runner(void *param){
int upper = atoi(param);
int i;
sum = 0;
if(upper > 0){
for(int i=1; i<=upper; i++){
sum += i;
}
}
pthread_exit(0);
}
Windows Threads
ChreateThread()
: 스레드 생성WaitForSingleObject()
: 한 스레드 종료를 기다림WaitForMultipleOjbects()
: 여러 스레드 종료를 기다림
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <windows.h>
DWORD Sum; /* data is shared by the thread(s) */
/* the thread runs in this separate function */
DWORD WINAPI Summation (LPVOID Param)
{
DWORD Upper = (DWORD *)Param;
DWORD i;
printf("start Summation Thread...\n");
for (i=0; i <= Upper; i++)
Sum += 1;
return 0;
}
int main(int argc, char *argv[]){
DWORD ThreadId;
HANDLE ThreadHandle;
int Param;
// do some basic error checking
if (argc = 2) {
fprintf(stderr, "An integer parameter is required\n");
return -1;
}
Param = atoi(argv[1]);
if (Param <0) {
fprintf(stderr, "an integer >= 0 is required \n");
return -1;
}
// create the thread
ThreadHandle = CreateThread(NULL, 0, Summation, &Param, 0, &ThreadId);
if (ThreadHandle != NULL) {
WaitForSingleObject(ThreadHandle. INFINITE):
CloseHandle(ThreadHandle)
printf("sum = %d\n",Sum);
}
}
Java Threads
- 자바 스레드는 운여체제가 아닌 Java 언어 수준에서 지원하고 JVM이 관리함
- main() method만으로 구성된 자바 프로그램은 JVM에서 단일 thread로 실행됨
- Thread 생성 방법
- Extending Thread class(파생 클래스 생성)
- Implementing the Runnable Interface(인터페이스 구현)
4.5 묵시적 스레딩
- Thread의 생성과 관리를 응용 프로그램 개발자가 아닌,
컴파일러와 런타임 라이브러리에게 넘겨주는 것 - 멀티코어 병렬처리를 사용하는 프로그램 설계에 이용함
- Open MP, GCD(Grand Central Dispath)는 컴파일러에서 멀티 쓰레딩 지원
- Thread Pool이라는 멀티 쓰레딩 관리 방법 이용
- Multithread Server에서의 잠재적 문제점
- 스레드 생성 오버헤드 : 요청마다 스레드를 생성하는데 시간이 소요됨
- 스레드 수가 증가에 따른 자원 고갈 가능성 존재. 스레드 수에 제한이 필요
- 해결책으로 Thread Pool 방식을 이용함
- 프로세스를 시작할 때에 일정한 수의 스레드들을 thread Pool에 미리 생성하여 대기
- 요청을 받을 때 마다, Pool에 있는 하나의 스레드를 깨워서 사용함
- 장점
- 빠른 속도 : 기존 스레드를 사용하므로 새 스레드 생성 불필요
- 동시 존재 스레드 수를 제한하여 자원 제한 가능(pool의 크기)
- task 생성 방법을 task에서 분리하면, task 실행을 주기적 실행, 일정시간 후 실행 등 다양한 방식으로 할 수 있음
4.6 스레드 관련 이슈
fork()
(프로세스 생성),exec()
(프로세스 덮어쓰기) 시스템 호출- 스레드에서
fork()
를 호출할 때의 동작은? - 두가지 구현 방법이 존재
- process의 모든 스레드들을 복제한다
- 해당 스레드만 복제한다
- 스레드에서
- UNIX에서의 Signal handling
- Signal :
- 프로세스에 대한 Signal은 CPU에 대한 Interrupt와 유사함
- UNIX에서 프로세스에게 특정 이벤트 발생을 알려주기 위해 사용
- 동기식 signal : illegal memory access, division by zero
- 비동기식 signal : Ctrl + C 종료 등
- signal handler : signal 을 처리하기 위해 사용됨
- signal 은 특정 이벤트에 의해 생성되어 프로세스에게 전달됨
- 프로세스의 signal handler에서 처리됨
- 두 종류의 signal handler
- default signal handler
- user-defined signal handler
- Multithreaded process에서의 signal 전달 옵션
- signal이 적용될 모든 스레드에게 전달
- 모든 스레드에게 전달
- 일부 thread에게 선택적 전달
- signal을 전달받을 스레드 지정 등
- Signal :
- 스레드 취소(cancellation)
- 타겟 스레드가 종료되기 전에 스레드를 강제로 중단 시키는 것
- 두가지 취소 방식
- 비동기(asynchronous) 취소 : 즉시 해당 스레드 취소
- 지연(deferred) 취소 : 주기적으로 점검하여 해당 스레드 취소
- 스레드 취소의 어려운 점
- 자원이 할당되어 있거나, 공유 데이터를 갱신하고 있는 스레드를 취소할 때 처리의 어려움
- 비동기 취소 방식에서 자원 회수에 문제 발생 가능성
- 지연 취소 방식에서는 취소 시점(cancellation point)에서 안전한 취소 가능 여부를 검사해야함
- Thread-Local Storage(TLS)
- 스레드 자신만이 접근 가능한 저장 공간
- local 변수 : 함수 호출 동안에만 일시적으로 사용 가능
- static 변수 : 프로그램 실행 동안 함수 내의 기억장소 유지
- TLS : static 변수와 유사하지만, 각 스레드별로 분리된 기억장소 사용
- Scheduler Activiations
- 경량 프로세스(lightweight Process :LWP)
- many-to-many 모델에서는 유저 레벨 스레드와 커널 스레드 사이에 LWP라는 중간 자료구조를 사용하여 연결함
- 각 LWP는 커널 스레드에 연결되며, 유저 스레드 라이브러리에게 가상 프로세서같이 보여짐
- 각 응용에 필요한 LWP 개수. CPU-Bound는 1개, I/O intensive 응용은 여러개 필요
- Scheduler Activation
- upcall : 스레드가 블록되거나, 이벤트가 발생하면, 커널에서 시그널을 보내어 스레드 라이브러리의 upcall handler를 호출함
- upcall handler는 새 LWP를 할당받아 실행되고, 다른 스레드에게 스케줄링함
- 운영체제는 kerenl 스레드를 스케줄링함
- 경량 프로세스(lightweight Process :LWP)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.