포스트

운영체제 4장 - 스레드

목차


4.1 개요

4.2 멀티코어 프로그래밍

4.3 다중 스레드 모델

4.4 Thread 라이브러리

4.5 묵시적 쓰레딩

4.6 스레드 관련 이슈

4.1 개요


  • Thread : CPU의 기본 실행 단위
  • 단일 스레드 프로세스 : 전통적인 프로세스. 한개의 실행 단위로 구성됨
  • 다중 스레드 프로세스 :
    • 여러개의 실행 스레드를 갖는 프로세스
    • 한 프로세스가 동시에 하나 이상의 작업 수행 가능
    • 전통적인 프로세스의 확장
  • 스레드의 사용 자원

4_1

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), 적응성
      • 다중 프로세서 구조에서 스레드들을 병렬 처리 가능
      • 규모가 커질수록 성능이 높아짐

4.2 멀티코어 프로그래밍


4_2_1

  • 단일 코어 시스템에서의 병행(concurrent) 실행
    • T1, T2, T3, T4 가 빠르게 번갈아가면서 실행됨

4_2_2

  • 멀티코어 시스템에서의 병렬(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 모델

4_3_1

  • 다수의 사용자 스레드 - 하나의 커널 스레드 관계
  • 스레드 스케줄링과 동기화가 사용자 공간의 스레드 라이브러리에서 수행됨
  • 장점 : 문맥교환이 적고, 동기화 오버헤드가 작아서 효율적
  • 단점 :
    • 한 스레드가 Blocking System Call을 호출하여 Block되면, 전체 프로세스가 블럭됨
      • 커널이 user-level 스레드의 존재를 알지 못함
    • 멀티프로세서 시스템에서 스레드들의 병렬 실행 불가
  • Solaris Green Threads, GNU Portable Thread 등
2. One-to-One 모델

4_3_2

  • 각 사용자 스레드가 한개의 커널 스레드에 연관됨
  • 장점 :
    • 더 많은 병행, 병렬 실행 가능
      • 한 스레드가 blocking system call을 호출하여 블록되어도, 커널은 같은 프로세스의 다른 thread로 스케줄링
  • 단점 :
    • 스레드 생성 및 문맥교환 오버헤드
      • 커널 시스템 호출을 사용하여 커널 스레드를 생성해야함
      • 커널에서 thread 문맥교환이 이루어짐
      • 최대 thread 수에 제한
  • windows, linux, solaris 9 이후 버전
3. Many-to-Many 모델

4_3_3

  • 다수의 사용자 레벨 스레드가 다수의 커널 스레드에 연관
    • 커널 스레드의 수는 사용자 레벨 스레드의 수와 작거나 같음
    • 커널 스레드들을 사용자 레벨 스레드들이 Multiplex하여 사용함
      • 사용자 라이브러리가 커널 스레드를 할당시켜줌
    • 커널 스레드 개수는 응용프로그램이나 기계에 따라 결정됨
  • 장점 :
    • one-to-one 모델과 같은 병행/병렬성
      • 필요한 만큼의 커널 스레드와 연관되는 경우
        • Blocking System Call을 호출하여 블록되어도, 다른 thread를 스케줄
    • 사용자 스레드 개수보다 적은 커널 스레드를 사용할 수 있어, 커널 스레드 수의 제한에 영향이 없

4_3_4

4. Two-Level 모델

4_3_5

  • 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 호출로 이어짐
  • 주로 사용되는 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)는 컴파일러에서 멀티 쓰레딩 지원
    • OpenMP(Open Multi-Porcessing)
      • C, C++, Fortran 컴파일러의 directive와 API로 multithreaded 프로그래밍 지원
    • GCD
      • Apple Mac OS X, iOS운영체제에서 지원

        Thread Pool

  • Thread Pool이라는 멀티 쓰레딩 관리 방법 이용
  • Multithread Server에서의 잠재적 문제점
    • 스레드 생성 오버헤드 : 요청마다 스레드를 생성하는데 시간이 소요됨
    • 스레드 수가 증가에 따른 자원 고갈 가능성 존재. 스레드 수에 제한이 필요
    • 해결책으로 Thread Pool 방식을 이용함
  • 프로세스를 시작할 때에 일정한 수의 스레드들을 thread Pool에 미리 생성하여 대기
  • 요청을 받을 때 마다, Pool에 있는 하나의 스레드를 깨워서 사용함
  • 장점
    • 빠른 속도 : 기존 스레드를 사용하므로 새 스레드 생성 불필요
    • 동시 존재 스레드 수를 제한하여 자원 제한 가능(pool의 크기)
    • task 생성 방법을 task에서 분리하면, task 실행을 주기적 실행, 일정시간 후 실행 등 다양한 방식으로 할 수 있음

4.6 스레드 관련 이슈


  • fork()(프로세스 생성), exec()(프로세스 덮어쓰기) 시스템 호출
    • 스레드에서 fork()를 호출할 때의 동작은?
    • 두가지 구현 방법이 존재
      1. process의 모든 스레드들을 복제한다
      2. 해당 스레드만 복제한다
  • 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을 전달받을 스레드 지정 등
  • 스레드 취소(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 스레드를 스케줄링함
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.