[운영체제 공룡책] 스레드
Posted: Updated:
운영체제 스터디를 하며 ‘운영체제 공룡책’ 교재를 정리한 글입니다.
스레드
스레드는 스레드 ID, 프로그램 카운터(PC), 레지스터 집합, 스택으로 구성된다.
같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일, 신호 등의 운영체제 자원을 공유한다.
프로세스가 다수의 제어 스레드를 가지면 동시에 하나 이상의 작업을 수행할 수 있다.
다중 스레드 프로그래밍
장점
- 응답성: 대화형 응용 프로그램을 다중 스레드화하면 시간이 많이 걸리는 연산을 비동기적 스레드에서 실행하여 사용자에게 계속 응답할 수 있다.
- 자원 공유: 프로세스 내 자원과 메모리를 공유할 수 있다.
- 경제성: 프로세스를 생성하는 것보다 스레드를 생성하여 context change하는 것이 더 경제적이다.
- 규모 적응성: 다중 처리기 구조에서 각각의 스레드를 병렬로 수행할 수 있다.
다중 코어 프로그래밍
단일 컴퓨팅 칩에 여러 컴퓨팅 코어를 배치하는 시스템을 다중 코어라고 한다.
다중 스레드 프로그래밍은 여러 컴퓨팅 코어의 병행성을 향상시키고 효율적으로 사용할 수 있게 한다.
단일 프로세서에서 스케줄러가 프로세스를 빠르게 전환하여 병렬성 없이 병행성을 가질 수 있도록 한다.
병렬 실행
- 데이터 병렬 실행: 동일한 데이터의 부분 집합을 다수의 계산 코어에 분배하고 각 코어에서 동일한 연산 실행
- 태스크 병렬 실행: 태스크를 다수에 코어에 분배하고 각 스레드는 고유 연산을 실행
다중 스레드 모델
사용자 스레드는 사용자 수준에서, 커널 스레드는 커널 수준에서 제공된다.
사용자 스레드는 커널 위에서 지원되며, 커널 지원 없이 관리되지만 커널 스레드는 운영체제에 의해 직접 지원되고 관리된다.
다대일 모델
많은 사용자 수준 스레드가 하나의 커널 스레드에 속한다.
한 스레드가 봉쇄형 시스템 콜 시 전체 프로세스가 봉쇄된다.
한 번에 한 스레드만 커널에 접근할 수 있어 병렬로 실행될 수 없다.
일대일 모델
각 사용자 스레드가 하나의 커널 스레드에 속한다.
한 스레드가 봉쇄적 시스템 콜을 호출해도 다른 스레드가 실행될 수 있다.
다대다 모델
여러 개의 사용자 수준 스레드를 그보다 작은 수 혹은 같은 수의 커널 스레드로 멀티플렉스 한다.
커널 스레드 수는 응용 프로그램이나 특정 기계에 따라 결정된다.
개발자는 필요한 만큼 많은 사용자 수준 스레드를 생성하고, 상응하는 커널 스레드가 다중 처리기에서 병렬로 수행될 수 있다.
또한 스레드가 봉쇄형 시스템 콜을 발생시켰을 때 커널이 다른 스레드의 수행을 스케줄할 수 있다.
한 사용자 스레드가 하나의 커널 스레드에만 연관되는 것을 허용하는 경우 두 수준 모델(two-level model)이라고 불린다.
다대다 모델은 구현하기가 어려우며, 대부분의 시스템에서 처리 코어 수가 증가하여 커널 스레드 수를 제한하는 것의 중요성이 줄어들었다.
따라서 대부분의 운영체제는 일대일 모델을 사용한다.
스레드 라이브러리
스레드 라이브러리는 커널의 지원 없이 사용자 공간에서만 라이브러리를 제공할 수도 있고, 운영체제에 의해 지원되는 커널 수준 라이브러리를 제공할 수도 있다.
현재 POSIX Pthreads, Windows, Java의 세 종류 라이브러리가 주로 사용된다.
비동기 스레딩은 부모가 자식 스레드를 생성하고 부모가 자신의 실행을 재개해 부모와 자식 스레드가 독립적으로 병행하게 실행된다.
동기 스레딩은 부모 스레드가 하나 이상의 자식 스레드를 생성하고 자식 스레드 모두가 종료할 때까지 기다렸다가 자신의 실행을 재개하는 방식이다.
Pthreads
POSIX가 스레드 생성과 동기화를 위해 제정한 표준 API이다.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int sum; /* 스레드에서 공유할 값 */
void *runner(void *param); /* 스레드가 호출하는 함수 */
int main(int argc, char *argv[])
{
pthread_t tid; /* 스레드 식별자 */
pthread_attr_t attr; /* 스레드 속성 */
/* 스레드 기본 속성 지정 */
pthread_attr_init(&attr);
/* 스레드 생성 */
pthread_create(&tid, &attr, runner, argv[1]);
/* 스레드 종료 기다리기 */
pthread_join(tid, NULL);
printf("sum = %d\n", sum);
}
void *runner(void *param)
{
int i, upper = atoi(param);
sum = 0;
for (i = 1; i <= upper; i++)
sum += i;
pthread_exit(0);
}
Windows 스레드
#include <windows.h>
#include <stdio.h>
DWORD Sum; /* 스레드에서 공유할 데이터 */
/* 스레드에서 실행할 함수 */
DWORD WINAPI Summation(LPVOID Param)
{
DWORD Upper = *(DWORD*)Param;
for (DWORD i = 1; i <= Upper; i++)
Sum += i;
return 0;
}
int main(int argc, char *argv[])
{
DWORD ThreadId;
HANDLE ThreadHandle;
int Param;
Param = atoi(argv[1]);
/* 스레드 생성 */
ThreadHandle = CreateThread(
NULL,
0, /* 스택 크기 */
Summation, /* 스레드에서 실행할 함수 */
&Param, /* 스레드 함수의 파라미터 */
0,
&ThreadId); /* 스레드 식별자 반환 */
/* 스레드 종료를 기다림 */
WaitForSingleObject(ThreadHandle, INFINITE);
/* 스레드 핸들러 닫기 */
CloseHandle(ThreadHandle);
printf("sum = %d\n", Sum);
}
Java 스레드
Java 프로그램에서는 스레드를 명시적으로 생성하는 두 가지 방법이 있다.
Thread 클래스에서 파생된 새 클래스를 만들어 run() 메소드를 재정의하거나, Runnable 인터페이스를 구현하는 클래스를 정의한다.
Runnable 인터페이스를 구현하는 예제는 아래와 같다.
class Task implements Runnable
{
public void run() {
System.out.println("I am a thread.");
}
}
Runnable을 구현하는 Thread 객체를 생성한다.
Thread worker = new Thread(new Task());
worker.start();
Thread 객체의 start() 메소드를 호출한다.
이후 메모리가 할당되어 JVM 내에 새로운 스레드가 초기화된다.
start() 메소드가 내부적으로 run() 메소드를 호출하고, 스레드가 JVM에 의해 수행되게 된다.
Java Executor 프레임워크
스레드 생성 및 통신에 대한 제어 기능을 향상시키는 병행 처리 기능이다.
Executor 인터페이스를 중심으로 스레드를 구성한다.
댓글남기기