프로세스의 개념은 두 가지 특성을 포함:
자원 소유(Resource Ownership)
- 프로세스는 프로세스 이미지 및 리소스(메인메모리, I/O장치, 파일)를 hold하기 위한 가상 주소 공간을 포함
- OS는 자원과 관련된 프로세스 간의 원치 않는 간섭을 방지하기 위한 보호 기능 수행
스케줄링/실행(Scheduling/Execution)
- 하나의 프로세스 실행은 다른 프로세스들과 인터리브(교차 실행)될 수 있다
- 프로세스는 실행 상태(running, ready..)와 스케줄링 정보(디스패치 우선순위)를 가짐
- 프로세스는 OS에 의해 스케줄되고 디스패치되는 실행 단위
두 가지 특성은 서로 독립적
dispatching 단위 : Thread (= lightweight process)
resource ownership 단위 : Process (= task)
스케줄링/실행(디스패칭)의 단위는 스레드(=lightweight process)이고
자원 소유의 단위는 프로세스이다
single-threaded approach: 하나의 프로세스당 하나의 실행 흐름만을 갖는 방식
- MS-DOS는 단 하나의 사용자 프로세스만을 지원
- 구버전 UNIX는 여러 사용자 프로세스를 지원
Multi-threaded approach
멀티스레딩(Multithreading) : OS가 하나의 프로세스 내에서 여러 개의 실행 흐름 동시에 지원하는 방식
- Java 런타임 환경 : 하나의 프로세스 안에서 여러 스레드를 실행
- Windows, Solaris, Linux 는 여러 프로세스를 지원, 각 프로세스는 다수의 스레드를 포함 가능
멀티스레드 환경에서 프로세스는 자원 할당의 단위이자 보호의 단위
프로세스 관련 요소:
- 프로세스 이미지를 저장하는 virtual address space
- 다음 자원에 대한 protected access
프로세서(CPU), 다른 프로세스들 (프로세스 간 통신을 위한), 파일, I/O 자원
스레드 요소:
- 실행 상태 (Running, Ready, Blocked) (TCB에 포함)
- 실행 중이 아닐 때를 위한 saved thread context (TCB에 포함)
- 실행 스택 (User stack(스레드 별)에 포함)
- 해당 프로세스의 메모리 및 자원에 대한 접근 권한 (PCB에 포함, 스레드 간 공유)
스레드가 실행 흐름의 단위이기 때문에, 프로세서 정보는 모두 TCB에 있다, PCB에는 자원에 대한 정보들이 있다

Process creation is heavy-weight
while thread creation is light-weight
응답성(Responsiveness)
프로세스의 일부가 차단되어도 실행을 계속할 수 있게 해줌
특히 UI에서 중요
자원 공유(Resource Sharing)
스레드는 동일한 프로세스의 자원을 공유하여 서로 간의 통신이 빠르고 효율적
shared memory나 message passing보다 더 쉬움
경제성(Economy)
프로세스 생성보다 생성 비용이 적게 듬
Thread switching은 프로세스 간 Context switching보다 오버헤드가 낮다
확장성(Scalability)
멀티스레드 프로세스는 멀티코어 아키텍처를 효과적으로 활용할 수 있다.
스케줄링과 디스패치가 스레드 단위로 이루지고 실행 관련 정보는 스레드 수준 데이터 구조에 저장
프로세스 내 모든 스레드에 영향을 주는 동작들
- 프로세스 중지하면, 해당 프로세스의 모든 스레드 함께 중지 → 모든 스레드가 같은 주소 공간을 공유하기 때문
- 프로세스를 종료하면, 해당 프로세스 내의 모든 스레드 함께 종료
스레드의 상태: Running / Ready / Blocked
스레드 상태 변화 관련 연산:
Spawn : 하나의 프로세스 내에서 한 스레드가 다른 스레드를 생성
Block : 스레드가 어떤 이벤트를 기다려야 할 경우, 해당 스레드는 blocked 상태로 전환
Unblock : blocked된 스레드가 기다리던 이벤트가 발생하면, 해당 스레드는 Ready queue로 이동
Finish : 작업이 완료 시, register context와 stack이 할당 해제됨
여러 스레드의 Synchronization 필수 :
하나의 프로세스에 속한 모든 스레드는 같은 주소 공간과 자원을 공유
따라서 한 스레드가 자원을 변경하면, 다른 스레드들도 그 영향을 받게 됨
User Level Thread (ULT) :
어플리케이션 라이브러리에 의해 관리되는 스레드로 커널은 스레드의 존재를 인지하지 못한다
Many-to-One 모델 :
여러 개의 User 스레드에 하나의 Kernel 스레드를 매핑하는 방식

장점
1. 스레드 전환 시 context switching이 필요하지 않아 전환이 빠르다
2. 스케줄링 우선순위를 프로그래머가 설정할 수 있다
3. 라이브러리로 돌아가기 때문에 어떤 OS에서도 실행 가능
단점
많은 시스템 콜이 블로킹됨
OS가 개별 ULT를 인식하지 못하고, 하나의 커널 스레드만 관리하기 때문에
1. 커널 스레드가 블로킹되면, 그 위에서 동작하던 모든 ULT도 블로킹 되어 실행되지 못하게 됨
커널이 하나의 프로세스를 한 번에 하나의 프로세서에만 할당하기 때문에
여러 개의 스레드가 있어도 병렬 실행이 불가능
2. 멀티프로세서 환경의 장점을 활용할 수 없다 (확장성이 떨어진다)
단점 해결책
1. 멀티프로세스로 애플리케이션 작성 : 스레드의 장점을 없애는 것
- 프로세스 전환은 스레드 전환보다 훨씬 큰 오버헤드가 발생
2. 자켓팅(Jacketing)
- 블로킹 시스템 콜을 논블로킹 시스템 콜로 래핑하는 것
- 어떤 스레드가 시스템 콜을 하려 할 때,
지금 당장 실행 불가능하거나 시간이 걸릴 것 같으면
자기 자신을 스스로 Blocked 상태로 바꾸고, 준비된 스레드에게 CPU를 넘기는 것
(커널 수준 x, ULT 라이브러리 내부 수준 o)
Kernel Level Thread (KLT) : 커널에 의해 관리되는 스레드
light weight process라고 하는 것은 kernel level 스레드이다
One-to-One 모델:
하나의 User 스레드에 하나의 Kernel 스레드를 매핑하는 방식
user-level 스레드를 생성하면, 해당하는 kernel 스레드도 함께 생성됨
애플리케이션 수준에서는 스레드 관리 코드가 필요 없으며, 단순히 커널 스레드 기능을 사용하는 API만 제공
각 user 레벨 스레드는 하나의 kernel 레벨 스레드에 1:1로 매핑
장점
1. 커널이 하나의 프로세스 내 여러 스레드를 여러 프로세서에 동시에 스케줄링 할 수 있어
Many-to-one 방식보다 concurrency가 높다
2. 한 스레드가 blocked 되더라도, 커널이 같은 프로세스의 다른 스레드를 대신 실행할 수 있다
단점
1. 스레드 간 제어를 전환할 때, context switch해야 하므로 오버헤드가 발생
이 오버헤드 때문에, 프로세스당 생성 가능한 스레드 수가 제한될 수 있음
Combined Approaches : Many-to-Many모델
- 스레드 생성은 user space에서 처리, 대부분의 스케줄링과 동기화 작업도 사용자 수준에서 수행
- 여러 개의 ULT가 일정 수(ULT 스레드보다 같거나 작은 양)의 KLT에 매핑
- 오류 많고, 관리의 오버헤드가 커서 현재는 잘 사용되지 않음
싱글-코어 시스템에서의 councurrent execution:
동시성은 둘 이상의 작업을 번갈아 수행하여 마치 동시에 수행되는 것 처럼 보이게 하는 것
멀티-코어 시스템에서의 parallelism:
병렬성은 둘 이상의 작업을 물리적으로 동시에 수행하는 것
멀티코어 시스템의 challenges:
(1) 작업 식별 (Identifying tasks)
- 병렬화 가능한 영역을 찾아야 함
- 작업들이 서로 독립적이어야 함
(2) 균형 (Balance)
데이터 분할(Data splitting)
- 데이터를 나누어 별도의 코어에서 실행할 수 있어야 함
(3) 데이터 의존성 (Data dependency)
- 작업 간 데이터 의존성을 고려하여 실행을 동기화해야 함
(4) 테스트 및 디버깅 (Testing and debugging)
- 단일 스레드 애플리케이션보다 더 어려움
데이터 병렬성(Data parallelism)
– 데이터의 하위 집합들을 여러 코어에 분산시켜, 각 코어에서 동일한 연산을 수행
작업 병렬성(Task parallelism)
– 동일한 데이터를 여러 코어에 분산시켜, 각 코어에서 고유한 작업을 수행
Amdahl’s Law :
추가적인 코어 사용에 따른 성능 향상 정도를 나타는 법칙

최대 속도 향상은 1 / S에 수렴함
- S = 5%, N = 2 → 속도 향상(Speedup) = 1.9
- S = 25%, N = 2 → 속도 향상 = 1.6
- S = 50%, N = 2 → 속도 향상 = 1.3
Pthreads Example
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int sum; /* this data is shared by the thread(s) */
void *runner(void *param); /* threads call this function */
int main(int argc, char *argv[])
{
pthread_t tid; /* the thread identifier */
pthread_attr_t attr; /* set of thread attributes */
/* set the default attributes of the thread */
pthread_attr_init(&attr);
/* create the thread */
pthread_create(&tid, &attr, runner, argv[1]);
/* wait for the thread to exit */
pthread_join(tid, NULL);
printf("sum = %d\n", sum);
}
/* The thread will execute in this function */
void *runner(void *param)
{
int i, upper = atoi(param);
sum = 0;
for (i = 1; i <= upper; i++)
sum += i;
pthread_exit(0);
}
#include <pthread.h>
#include <stdio.h>
int value = 0;
void *runner(void *param); /* the thread */
int main(int argc, char *argv[])
{
pid_t pid;
pthread_t tid;
pthread_attr_t attr;
pid = fork();
if (pid == 0) { /* child process */
pthread_attr_init(&attr);
pthread_create(&tid, &attr, runner, NULL);
pthread_join(tid, NULL);
printf("CHILD: value = %d", value); /* LINE C */
}
else if (pid > 0) { /* parent process */
wait(NULL);
printf("PARENT: value = %d", value); /* LINE P */
}
}
void *runner(void *param) {
value = 5;
pthread_exit(0);
}