Overview
- 하나의 프로세스는 메모리 상의 이미지인 코드, 데이터, 스택 등으로 구성
- 실행 중인 프로세스는 프로그램 카운터를 포함한 레지스터를 가짐
- single-threaded process : 전통적인 프로세스는 프로세스 안에 thread가 하나인 프로세스

- multithreaded process
- 코드, 데이터, 파일은 공유
- 레지스터, 스택은 각 thread마다 가짐
→ 각 실행을 대표하는 내용, 즉 실행시마다 달라지는 레지스터와 스택을 합쳐서 thread라고 부름
- 실행의 기본 단위는 process가 아닌 thread
→ scheduling의 대상도 thread
- thread를 'light-weight process'라고도 함
- 현대 OS들은 kernel 자체가 다수의 thread로 이루어져 있음
Multithreaded Server Architecture

- 네트워크 서버 프로그램의 동작
- 클라이언트로부터 서비스 요청이 들어오면 사용자 요청을 대기하고 있던 thread가 요청을 받아서 이를 처리할 수 있는 서비스 thread를 생성하고, 요청에 대한 처리를 넘김
- 서버는 또 다른 클라이언트 요청을 반복해서 대기하는 구조
- 하나의 웹 서버 프로세스가 웹으로부터의 요청을 받는 하나의 thread와 다수의 클라이언트를 담당하는 thread 등으로 이루어짐
장점
- Responsiveness
- interactive한 응용의 경우 프로그램의 일부가 block 되거나 긴 작업을 수행하더라도 프로그램의 다른 부분의 수행이 계속되는 것을 허용할 수 있음
- 사용자 응답성 증가
- Resource Sharing
- 프로세스는 공유 메모리나 message 전달로 통신을 해야 했지만, thread는 자신이 속한 프로세스의 다른 thread들과 메모리 등 각종 자원을 공유 가능
- Economy
- 프로세스를 생성하기 위해 메모리와 자원을 할당하는 것은 많은 비용이 드는 작업
- thread는 자신이 속한 프로세스의 자원을 이미 공유하고 있으므로, thread만 생성 후 이들 간의 문맥을 교환하는 것이 훨씬 경제적임
- Scalability
- single-threaded 프로세스의 경우 다중처리기 상에서 수행되더라도 한 처리기에서만 실행됨
- multi-threaded 환경에서는 각 thread가 다른 CPU에서 병렬로 수행 가능
Multicore Programming
- Multiprocessor : 여러 개의 CPU를 가지는 컴퓨터 시스템
- Multicore : 단일 CPU 칩 안에 명령을 실행할 수 있는 컴퓨팅 코어가 여러 개인 시스템
- OS 같은 소프트웨어 입장에서는 멀티코어 상에서의 각 코어가 독립적인 CPU로 보임

- Parallelism
- 여러 개의 task를 병렬로 실행시킬 수 있음
- multi-threaded 프로그램은 병렬 실행을 할 수 있는 방법을 제시

- Concurrency
- 병행 수행은 여러 개의 task가 동시에 진행되도록 하는 것
- CPU가 하나인 경우에도 가능
- multi-programming은 병행 수행을 추구하는 기법
- Data parallelism
- 전체 데이터 집합을 다수의 부분 집합으로 나누고 이를 다수의 컴퓨팅 코어에 분배해서 각 코어가 동일한 연산을 실행하게 하는 경우
- single-core에서의 계산보다 실행시간이 절반으로 줄어듦
- Task parallelism
- thread가 각각 동일한 작업을 하는 것이 아닌 각기 다른 작업을 하는 경우
- Multicore 또는 Multiprocessor 시스템에서의 과제
- Dividing activities : 응용을 분석해서 주어진 응용이 서로 독립적인 병렬로 실행 가능한 task들로 나눌 수 있는지, 어느 부분에서 나뉘는지 찾는 작업이 필요
- Balance : 병렬 실행이 가능하도록 여러 개의 task로 분리했을 때 각 부분이 전체 작업에 대해 균등한 기여도를 가지도록 나누는 것이 중요
- Data splitting : task가 접근하는 데이터 또한 코어에서 사용할 수 있도록 나눠져야 함
- Data dependency : task가 접근하는 데이터가 둘 이상의 task가 접근했을 때 종속성이 있는지 검토해야 함
- Testing and debugging : 프로그램이 다중 코어 상에서 병렬 실행될 때 실행 경로가 여러가지 존재할 수 있으므로 모든 경로가 올바른 실행인지 검증할 필요가 있음
Amdahl's Law
- 순차 실행을 해야 되는 구성요소와 병렬 실행이 가능한 구성요소를 동시에 가지는 응용이 있을 때 이 응용을 멀티코어에서 실행함으로써 얻을 수 있는 성능 향상을 나타내는 공식

- S : 한 프로그램에서 순차 실행해야 되는 부분의 비율
- N : 코어의 개수
- N이 무한대로 커지면 성능 향상은 1/S에 수렴하게 됨
→ 성능 향상의 최대치가 1/S
- 응용에 포함된 순차 실행은 코어를 추가해서 얻을 수 있는 성능 향상에 불균형적인 영향을 미치게 됨
- 암달의 법칙은 코어의 개수를 늘리면 성능이 향상되지만 성능 향상에는 물리적 한계가 있다는 것을 시사함
Multithreading models
- User thread
- 사용자 수준에서 동작
- 사용자 수준 thread는 thread library를 통해 사용 가능
- thread library를 사용하면 하위의 OS kernel에서 thread 기능을 제공하는지 여부에 상관없이 thread를 만들어 사용 가능
- Thread libraries
- POSIX Pthreads
- Window thread
- Java threads
- Kernel thread
- kernel 수준에서 제공하는 thread
- kernel 자체도 여러 개의 thread로 이루어져 있음
- User thread와 Kernel thread는 둘 사이 연관 관계가 있어야 함
- Many-to-One
- One-to-One
- Many-to-Many
Many-to-One

- 여러 개의 사용자 수준 thread가 하나의 kernel thread에 연관되어 있음
- 여러 개의 user thread 중 하나가 blocking 시스템콜을 하게 되면 그에 따라 유일한 kernel thread가 block 상태가 되어 나머지 thread들도 원치 않게 block 상태가 됨
- 한 번에 하나의 thread만이 kernel에 접근 가능하므로, 여러 thread가 다중코어 시스템에서 병렬로 실행
- 무늬만 다중 thread 형태임
- 실제로는 한 번에 한 thread만 실행 가능
- 예시
- Solaris Green Threads
- GNU Portable Threads
One-to-One

- 각 user thread가 kernel thread 하나와 일대일 대응 되어있는 모델
- user thread 하나를 만들면 그에 대응하는 kernel thread가 하나 생성됨
- Many-to-One 모델보다 더 많은 병렬성 제공
- 너무 많은 수의 kernel thread가 생성되어 시스템에 부담을 줄 수 있으므로 사용자 수준에서 프로세스당 만들 수 있는 thread의 개수가 제한되는 경우가 많음
- 예시
- Windows
- Linux
- Solaris 9 and later
Many-to-Many

- 다수의 사용자 thread를 다수의 kernel thread에 연관시키는 모델
- 여러 개의 사용자 thread를 그보다 작거나 같은 수의 kernel thread로 multiplex하는 방식
- multiplex : 어느 사용자 thread가 어느 kernel thread와 연관될지는 그때그때 달라짐
- kernel이 미리 적절한 수의 kernel thread를 생성하고 필요한 user thread가 생길 때마다 이를 적당한 kernel thread에 대응시킴
- Many-to-One과 One-to-One 모델의 단점을 보완
- 응용 개발자 입장에서는 필요한 만큼 많은 사용자 수준의 thread 생성 가능
- 이에 상응하는 kernel thread가 멀티프로세서 상에서 병렬 수행 가능
- 예시
- Solaris prior to 9
- Windows with the ThreadFiber package
Two-level
- 한 시스템 내에서 Many-to-Many 모델과 One-to-One 모델을 혼용하는 방식
- 기본적으로는 Many-to-Many 모델을 사용하되, 일부 thread의 경우 One-to-One 방식으로 user와 kernel thread가 연관될 수 있음
- 예시 (과거 일부 유닉스 계열 OS가 사용하던 방식)
- IRIX
- HP-UX
- Tru64 UNIX
- Solaris 8 and earlier
Thread Libraries
- 프로그래머에세 thread를 생성하고 관리할 수 있는 API를 제공
- thread library 구현 방법
- 순수하게 user level에서 구현
- OS가 지원하는 kernel 수준 library
- POSIX Pthreads / Windows Threads / Java Thread
Pthreads
-
대표적으로 널리 사용되며, user 수준 또는 kernel 수준에서 실행 가능
-
IEEE 1003.1c 표준을 준수하는 library
-
대부분의 유닉스 계열 OS는 Pthread를 지원
-
Pthread library를 사용해서 숫자 n을 입력 받고 1~n까지 정수의 합을 구해서 출력하는 프로그램
#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;
if (argc != 2) {
fprintf(stderr, "usage: a.out <integer value>\n");
return -1;
}
if (atoi(argv[1]) < 0) {
fprintf(stderr, "%d must be >= 0\n", atoi(argv[1]));
return -1;
}
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);
}
- sum : main thread와 runner thread 간의 결과값을 주고받을 수 있는 전역변수
- pthread_attr_init(&attr)
- Pthread는 새로운 thread를 만들 때 thread의 속성을 담을 변수가 필요
- 변수는 사용하기 전에 pthread_attr_init 함수를 이용해 속성 변수 attr을 초기화
- pthread_create(&tid, &attr, runner, argv[1])
- tid : thread의 id. Pthread는 새로운 thread가 생성될 때마다 새로운 id를 부여
- attr : thread의 속성을 담는 변수
- runner : 함수의 이름(포인터). Pthread에서는 새로 생성되는 thread가 실행할 코드를 함수 단위로 지정
- argv[1] : 명령줄에 표시된 숫자 n을 가리킴
- 4번째 parameter는 3번째 parameter에 지정된 함수가 실행될 때 이 함수에 대한 parameter로 주어지게 됨
- pthread_join(tid, NULL)
- 특정한 thread의 종료를 기다리는 동작
- 어떤 thread를 기다릴지는 첫 번째 parameter로 명시
Windows Multithreaded C Program
#include <windows.h>
#include <stdio.h>
DWORD Sum;
DWORD WINAPI Summation(LPVOID Param)
{
DWORD Upper = *(DWORD*)Param;
for (DWORD i = 0; i <= Upper; i++)
Sum += i;
return 0;
}
int main(int argc, char *argv[])
{
DWORD ThreadId;
HANDLE ThreadHandle;
int Param;
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;
}
ThreadHandle = CreateThread(
NULL,
0,
Summation,
&Param,
0,
&ThreadId);
if (ThreadHandle != NULL) {
WaitForSingleObject(ThreadHandle, INFINITE);
CloseHandle(ThreadHandle);
printf("sum = %d\n", Sum);
}
}
- Sum : Thread 간에 공유되는 전역변수
- Summation : 새로 생성된 thread의 코드에 해당하는 함수
- 별도의 종료 함수가 따로 있지 않고 thread 함수가 return 하는 것으로 thread가 종료됨
- Param : Summation 함수에 주어질 parameter
Implicit Threading
- multi-programming이 많이 사용되면서 하나의 프로그램이 수백 개의 thread로 구성되기도 함
→ 프로그램이 올바르게 동작한다는 것을 검증하는 것이 어려워짐
- implicit threading : thread의 생성과 관리를 프로그래머가 아닌 컴파일러나 run-time library 등이 대신 해줌
- 대표적인 기법
- Thread Pools
- OpenMP
- Grand Central Dispatch
Thread Pools
- 프로세스를 시작할 때 미리 일정한 수의 thread들을 pool 형태로 만들어 놓는 것
- pool에 속하는 thread들은 평소에 아무것도 하지 않고 대기 상태로 존재하다가 요청이 발생하면 할당됨
- 장점
- 미리 thread를 만들어둠으로써 필요시에 생성하는 것보다 전체 동작이 빨라짐
- thread가 무한정 늘어나는 것을 방지
- task의 실행을 생성과 분리함으로써 실행에 대한 다양한 전략 적용 가능
OpenMP
- C나 C++, FORTRAN 같은 언어들을 위한 일련의 컴파일러 directive와 API의 집합
- parallel region : OpenMP에서 병렬로 실행 가능한 부분
- 개발자는 자신의 코드 중 parallel region에 관련 컴파일러 directive를 삽입함으로써 OpenMP run-time library에 해당 영역을 병렬로 실행하도록 요청할 수 있음
Grand Central Dispatch
- 애플 사에서 Mac OS를 위해 개발한 기술로, C language, API, run-time library를 확장한 기술
- C언어를 확장해서 block을 정의해 사용
- "^{ }"을 하나의 block으로 정의
- 예시 : ^{ printf("I'm a block"); }
- block을 dispatch queue에 넣어서 나중에 실행이 될 수 있도록 스케줄할 수 있음
- queue에서 block을 하나씩 꺼내어 해당 block을 thread pool에서 꺼낸 가용 thread에 할당해서 실행
- dispatch queue 타입
- serial
- first-in-first-out 방식으로 block을 꺼냄
- 꺼내진 block은 또 다른 block을 꺼내기 전 실행이 되도록 보장함
- 프로세스마다 serial queue를 하나씩 갖고 있으며 이를 main queue라고 함
- 프로그래머가 추가로 생성 가능
- concurrent
- first-in-first-out 순으로 block을 꺼내는데, 한 번에 여러 개를 꺼낼 수 있음
- 여러 개를 꺼내는 경우 이들은 동시 실행 가능
- 시스템 전체적으로 3개의 concurrent queue가 있음 (우선순위에 따라 나뉨)
참고 자료 : Operating System Concepts Essentials
*이미지 자료는 교재 자료를 직접 다시 만든 것으로 무단 불펌 금지입니다
제일 좋아하는 파트가 점점 다가와서 설렌다
끗!!