프로세스 생성의 오버헤드가 크다. (메모리 할당 -> fork() -> PCB -> Page(segment) mapping table -> ...)
Process context switching의 오버헤드가 크다.
프로세스간의 통신이 어렵다.
이러한 프로세스의 문제점들을 보완하기 위해서 쓰레드가 등장했다.
프로세스는 운영체제 작업단위, 쓰레드는 CPU 작업단위라고 생각할 수 있다. 쓰레드를 lighweight process라고 부르기도 한다.
쓰레드의 장점은 프로세스의 단점을 보완한 것으로 프로세스의 생성 및 소멸에 따른 오버헤드 감소, 빠른 Context swtiching, 손쉬운 통신이다.
프로세스는 반드시 1개 이상의 쓰레드로 구성되어야 한다.
다른 쓰레드들은 함수를 쓰레드로 만들어줄 것을 요청하여 생성되고 각 쓰레드 별로 TCB(Thread control block)이 생성된다. 그리고 이 TCP는 PCB에 등록된다. 커널은 CPU 스케줄링 시 전체 TCB 중 하나를 선택하여 스레드를 실행시킨다.
프로세스는 쓰레드들의 공유 공간(환경)을 제공한다.

쓰레드의 생명과 프로세스의 생명
다음 코드는 두 쓰레드가 있는데, 하나의 쓰레드는 공유 변수의 값을 1씩 증가시키고 다른 쓰레드는 공유 변수의 값을 1씩 감소시킨다. 하지만 쓰레드의 실행 순서가 보장되지 않아 공유자원(전역변수)의 합이 0이 되지 않는다. 이러한 쓰레드의 공유 자원 문제는 동기화가 필요하다.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int sum = 0;
void* myThread1(void *p) {
printf("\t myThread 1 starts\n");
int *i = (int*)malloc(sizeof(int));
for (i = 0; i < (*(int*)p); i++) sum += 1;
return (void*)i;
}
void* myThread2(void *p) {
printf("\t myThread 2 starts \n");
int *i = (int*)malloc(sizeof(int));
for (i = 0; i < (*(int*)p); i++) sum -= 1;
return (void*)i;
}
int main() {
pthread_t tid1, tid2;
int count = 200000;
int *ret1, *ret2;
pthread_create(&tid1, NULL, myThread1, &count);
printf("myThread1's tid: %0X \n", (int)tid1);
pthread_create(&tid2, NULL, myThread2, &count);
printf("myThread2's tid: %0X \n", (int)tid2);
pthread_join(tid1, (void**)&ret1);
pthread_join(tid2, (void**)&ret2);
printf("myThreads have been finished \n");
printf("sum = %d\n", sum);
printf("ret1 = %d\n", (int)ret1);
printf("ret2 = %d\n", (int)ret2);
return 0;
}
실행결과
myThread1's tid: 6B4BB000
myThread 2 starts
myThread 2 starts
myThread2's tid: 6B547000
myThreads have been finished
sum = -69786
ret1 = 200000
ret2 = 200000
장점
단점
: 쓰레드가 생성되고 실행되는 동안 접근 가능한 메모리 영역
쓰레드 사적 공간
쓰레드 사이의 공유 공간
다음 코드에서 gsum은 전역변수로 공유되기 때문에 실행시마다 동일한 값을 보장하지 못하는 반변, tsum은 쓰레드 로컬 스토리지로 쓰레드마다 고유하기 때문에 동기화를 하지 않아도 안전하게 사용할 수 있다.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int gsum = 0;
int __thread tsum = 1;
void func(int a) {
printf("5_%d. gsum= %d \ tsum= %d \n", a, gsum, tsum);
int b = a + 10;
gsum += b;
tsum += b;
printf("6_%d. gsum=%d \ tsum= %d \n", a, gsum, tsum);
}
void* myThread(void *p) {
int a = (*(int*)p);
printf("2_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
for (int i = 0; i < 30000000/a; i++)
gsum += a;
tsum += a;
printf("3_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
func(a);
printf("7_%d. gsum= %d \ tsum= %d \n", a, gsum, tsum);
}
int main() {
pthread_t tid[2];
int arg[2] = {1000, 3000};
printf("1_main. gsum= %d / tsum= %d \n", gsum, tsum);
pthread_create(&tid[0], NULL, myThread, &arg[0]);
pthread_create(&tid[1], NULL, myThread, &arg[1]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
printf("8_main. gsum= %d / tsum= %d \n", gsum, tsum);
return 0;
}
실행 결과
1_main. gsum= 0 / tsum= 1
2_3000. gsum= 0 / tsum= 1
2_1000. gsum= 0 / tsum= 1
3_3000. gsum= 30000000 / tsum= 3001
5_3000. gsum= 22514000 tsum= 3001
6_3000. gsum=23101010 tsum= 6011
7_3000. gsum= 23624000 tsum= 6011
3_1000. gsum= 46698000 / tsum= 1001
5_1000. gsum= 46698000 tsum= 1001
6_1000. gsum=46699010 tsum= 2011
7_1000. gsum= 46699010 tsum= 2011
8_main. gsum= 46699010 / tsum= 1

쓰레드 생성
pthread_create)쓰레드 종료 (vs 프로세스 종료)
pthread_exit() 과 같이 쓰레드만 종료하는 함수 호출 시 해당 쓰레드만 종료pthread_exit()을 부르면 역시 main 쓰레드만 종료<-> 프로세스 종료
쓰레드 조인(join)
쓰레드 양보 (yield)
yield()와 같은 함수 호출을 통해 자신의 실행을 중단하고 다른 쓰레드를 스케줄하도록 지시 TCB(Thread control block)
현재 실행 중인 쓰레드를 중단시키고, 다른 쓰레드들에게 CPU를 할당한다. 현재 CPU 컨텍스트를 TCB에 저장하고 다른 TCB에 저장된 컨텍스트를 CPU에 적재한다.
CPU 레지스터 저장 및 복귀
커널 정보 수정
Context switching은 상당히 비싼 연산 중 하나이다. Context switching의 시간이 길거나 잦은 경우 컴퓨터 처리율이 심각하게 저하될 수 있다.
동일한 프로세스의 다른 쓰레드로 스위칭되는 경우
다른 프로세스의 쓰레드로 스위칭하는 경우
(다른 프로세스로 교체되면, CPU가 실행하는 주소 공간이 바뀌는 큰 변화로 인한 추가적인 오버헤드가 발생한다.)
커널 쓰레드: 커널이 직접 생성하고 관리하는 쓰레드
순수 커널 레벨 쓰레드(pure kernel-level thread)
사용자 쓰레드: 라이브러리에 의해 구현된 일반적인 쓰레드
멀티쓰레드의 구현 - 응용 프로그램에서 작성한 쓰레드가 시스템에서 실행되도록 구현하는 방법
Many-to-One 'N:1' model - N개의 사용자 레벨 쓰레드를 1개의 커널 쓰레드로 매핑
One-to-One '1:1" model - 1개의 사용자 레벨 쓰레드를 1개의 커널 레벨 쓰레드로 매핑
Many-to-Many 'N:M' model - N개의 사용자 레벨 쓰레드를 M개의 커널 레벨 쓰레드로 매핑