충남대학교 컴퓨터공학과 류재철 교수님의 "운영체제 및 실습" 강의를 필기한 내용입니다.
다소 잘못된 내용과 구어적 표현 이 포함되어 있을 수 있습니다.
쓰레드가 필요한 이유
- fork()를 하면 프로세스가 pid만 다르고 그대로 복제되는데 그러면 이 resource들을 공유하면 어떨까 하는 생각에서 나옴
- 왜냐하면 fork를 통해 매번 복사를 해 메모리에 할당되면 메모리도 많이 잡아먹고 복사하는데 시간이 걸리므로 오래 걸린다 이말이야
- 그래서 resource는 공유하고 dispatch만 다르게 해 시간과 메모리를 절약하자는 생각이다
- text와 data는 공유하면서 스택만 여러개로 복제되는 구조 - 이 하나하나의 스택들을 Thread라고 한다
- 그래서 이제는 실행의 단위가 프로세스가 아니라 프로세스 내의 Thread가 된다
- 그리고 이 thread들의 정보를 저장하는 놈이 TCB이다 - PCB와 별개로 쓰레드들마다 자신의 정보를 담고 있는 TCB가 생기게 된다
- 그래서 이제는 fork를 할때 프로세스 전체에 대한 공간을 확보하는게 아니고 스택이랑 TCB로 이루어진 thread만 확보하면 된다
- 이 때문에 thread를 light-weight process라고 부른다
- dispatch의 단위는 thread가 되고 resource의 단위(resource ownership이라 한다)는 process가 되는 것이다
- 하지만 process는 여전히 protection의 단위가 된다 - 어차피 thread는 데이터를 공유하므로 protection할 필요가 없더라
- 그래서 이제는 execution state도 thread단위로 일어나게 되고 context change가 일어나는 것도 thread단위로 일어나게 되며 실행되다가 cpu에서 물러날때 문맥저장도 쓰레드 단위로하게 된다
- 쓰레드의 장접은 다음과 같다
- 가볍기 때문에 fork, terminate, context-change가 빠르다 - context-change가 빠르기 때문에 concurrent processing에서도 이점이 있다
- 그리고 같은 프로세스여도 여러 thread를 가질 수 있기 때문에 하나의 프로세스가 실행되다 block을 먹어 기다려야 되는 상황에서도 process change없이 thread change를 통해 하나의 프로세스를 계속 이어나갈 수 있다
- 또한 정보를 공유하기 때문에 IPC에서도 이점이 있다
예시 - 웹 서버에서의 쓰레드
- 서버에서는 클라이언트의 리퀘스트가 들어오면 이 이것을 처리하는 프로세스로 처리하는게 아니라 자식 프로세스를 fork해서 처리하게 한다
- 이렇게 하는 이유는 자기가 직접 처리해버리면 이것을 처리하는 동안에는 다른 클라이언트의 리퀘스트를 받지 못하기 때문
- 근데 쓰레드 없이 fork하는 것은 프로세스 전체를 다 복사해야 하므로 오래걸린다 - 이것을 thread로 처리하면 작업속도를 많이 올릴 수 있게 된다
예시 - 함수 병행 처리
- 함수를 호출하는거를 RPC(Remote Procedure Call) 이라고 하는데 이렇게 RPC를 하게 되면 그 callee가 처리되고 처리되는동안 caller는 놀게 된다
- 근데 이제 쓰레드를 이용하면 하나의 함수를 call해서 처리하는 동안 다른 함수를 다른 쓰레드로 실행시키면 이 둘이 context switch되며 평행하게 실행되게 된다
Thread의 상태
- Spawn : fork에 대응
- Block : 프로세스에서의 Block과 같다
- Unblock : ready에 대응
- Finish : terminate에 대응
User-level thread(ULT), Kernel-level thread(KLT)
- User-level thread(ULT) : 쓰레드의 생성이 user mode에서 일어나는 것 - 리눅스 POSIX표준의 p_thread가 여기에 해당한다
- Kernel-level thread(KLT) : 쓰레드의 생성이 kernel mode에서 일어나는 것 - 윈도우계열 쓰레드들이 여기에 해당한다
- ULT 는 실행되다가 block을 먹으면 ULT의 경우에는 user mode에서 라이브러리의 도움을 받아 생성된 것 이므로 kernel에서 보기에는 그냥 하나의 프로세스처럼 보인다 - 때문에 그냥 process를 block시켜버린다
- 하지만 KLT는 block을 먹어도 kernel-level에서 실행되기 때문에 이놈이 thread인것을 알고 thread 하나만 block을 먹인다
- 따라서 ULT는 block을 먹으면 그 process내에 있는 thread전부가 block을 먹게 되고, KLT는 block을 먹어도 그 thread하나만 block을 먹게 된다*
- 이렇게 KLT의 경우 process전체가 block을 먹으면 thread가 가지는 concurrent의 이점을 가질 수 없기 때문에 상대적으로 느리다 - multi-thread로 짜나 uni-thread로 짜나 별반 차이가 없으니까 IO request가 많은 등의 블락을 많이 먹을거같으면 process가 dispatch의 단위가 되도록 프로그래밍 하는 것이 나을 때도 있다
- 하지만 대신 ULT의 경우 kernel과 무관하게 실행될 수 있으므로 os에 자유롭게 구동된다 - multi-platform하게 구동될 수 있다
- KLT는 kernel mode로 들어가서 구동해야 하므로 실행시간이 느리다는 단점이 있다 - 하지만 ULT와는 다르게 쓰레드 하나만 블락을 먹는다는 multi-thread의 장점때문에 결과적으로는 더 빠르게 구동된다