본문 바로가기

운영체제/프로세스(process)

운영체제 - 멀티 쓰레드(Multi-Thread)

운영체제 - 멀티 쓰레드(Multi-Thread)


Reactive System은

Iterative Server와 concurrent Server로 나뉜다.

1. Iterative Server는 실행 흐름이 한개 이므로 1개의 작업을 처리하는 도중 요청된 다른 작업을 처리할 수 없다.

2. concurrent Server는 여러개의 실행흐름을 가지므로 다른 작업을 처리하던 도중에도 요청된 다른 작업을 처리할 수 있다.

그러나, concurrent Server는 여러개의 process를 관리해야 하므로 그로인한 오버헤드(overhead)가 매우 컸다.

이 문제점을 해결하기 위해 concurrency는 높이면서 execution unit을 생성·소멸에 드는 비용을 줄이기 위해 multi-threading 개념이 도입되었다.


Process는 context + execution stream 이라고 정의할 수 있는데, 기존의 process가 1개의 context와 1개의 execution stream을 가졌다면 multi-threading인 process는 1개의 context와 1,000개의 execution stream을 가질 수 있다.

(즉, execution stream 수를 늘려 concurrency를 증가시켰으나 process context가 1개이므로 운영체제에서 process를 관리하는 데 필요한 비용이 적게든다. - PCB, context-switching, ...)


Multi-Threading이 필요한 이유

1. 적은 비용으로 concurrency를 얻기 위해(=response time을 줄임)

2. Massively Parallel Scientific Programming을 할 때 발생하는 오버헤드를 줄이기 위해


결론적으로 thread는 execution stream이며 cpu를 할당받는 주체이다.


Q. thread는 어떻게 구현되는 것인가?

thread : sequence of executed function. 으로써 stack으로 구현된다.

한개의 프로세스(process)는 쓰레드별로 여러개의 stack을 갖고 각 thread를 관리하는 thread id를 부여하고 이를 TCB(Thread Control Block)으로 관리한다.

각각의 thread는 해당 프로세스에게 부여된 resource와 memory address space를 공유한다.


좌측 Process : 1개의 process context + 1개의 stack

우측 Process : 1개의 process context + 3개의 thread context + 3개의 stack (3개의 실행흐름(execution stream)을 같은 멀티-쓰레드)


Q. 왜 멀티쓰레딩으로 concurrency를 구현한 것이 멀티프로세스로 concurrency를 구현한 것보다 오버헤드가 적을까?

- 프로세스를 1,000개를 구현해서 concurrency를 지원하면 1,000개의 process context + context-switching 비용이 든다.

그러나, 멀티-쓰레드 1,000개를 구현한다면 1개의 process context + 쓰레드를 위한 스택으로 구성되므로 비용이 훨씬 적다.


멀티쓰레딩은 그 구현방식에 따라 크게 3가지로 나뉜다.

  1. User-level threading(kernel이 쓰레드를 전혀 모르게 하는 방법)
  2. Kernel-level threading(kernel이 쓰레드를 100% 알고 있는 방법)
  3. 1,2의 mixture

1. User-level threading



user-level threading 구현방법

  1. 쓰레드(thread)를 위한 스택 생성
  2. TCB(Thread Control Block, thread context)를 생성하고 테이블로 관리
  3. 쓰레드 간의 context-switching이 일어나므로 thread scheduler가 필요 + dispatcher
위 3가지를 kernel-level이 아닌 user-level에서 라이브러리 형식으로 만들어야 한다.
그러나, user-level threading은 아래와 같은 한계를 갖는다.

Scheduling는 두가지로 나눌 수 있다.
1) preemptive scheduling : H/W Interrupt요구
2) non-preemptive scheduling : 자발적인 cpu 양보(yield)
그런데, user-level threading에서 Interrupt가 발생하면 kernel은 어떤 프로세스에서 interrupt가 발생한 지는 파악할 수 있으나 쓰레드의 존재를 모르므로 특정 쓰레드에 해당 interrupt를 전달할 수 없다. 즉, user-level threading에서는 preemptive scheduling방식이 불가능하다.
또한, user-level threading에서는 한 개의 쓰레드가 sync I/O operation을 호출하여 blocking되어도 같은 프로세스에 속한 모든 thread가 blocking되는 문제가 있다.( kernel이 쓰레드의 존재를 모르기 때문이다.)

User-level threading의 문제
  1. 한 thread가 Blocking System call을 호출하는 경우 해당 process의 모든 thread가 block됨.
  2. 운영체제가 thread에게 직접 interrupt를 전달할 수 없기 때문에 preemptive scheduling을 할 수 없음.

그러나, scientific programming에서는 유용하다.


2. kernel-level threading

- 쓰레드의 관리를 모두 kernel에서 관리하므로 User-level threading에서 발생하는 문제가 없다.

그러나, kernel단의 수행시간(오버헤드)가 증가한다.





3. UL/KL threading( mixture 형식 )

- SUN microsystems에서 개발한 solaris 운영체제의 threading 방법이다.




Q. 응용 프로그래머는 threading을 어떻게 사용하는가?

- multi-threading이 제공하는 API를 적절히 사용해야 한다.

ex. POSIX API : UNIX의 변종이 다양해져서 약속을 정해놓은 것


pthread_create()

매개변수로 function pointer를 전달하여 thread가 실행할 job을 전달함. 

pthread_terminate()

이 함수를 호출한 thread를 종료(terminated)시킨다. 

pthread_join()

매개변수로 전달받은 thread를 기다린다.(synchronous) 

pthread_yield() 

CPU를 양보(yield)한다. (non-preemptive scheduling) 



Q. pthread_yield()를 호출한 쓰레드는 어떤 식으로 수행이 재개되는가?

- pthread_yield()에 의한 쓰레드의 상태변화는 운영체제에 의해 관리된다. User code상에서는 pthread_yield()의 호출지점에서 정지되는 것으로 보이며, 쓰레드가 다시 CPU를 할당받으면 다음 statement부터 수행을 재개한다.



< thread life cycle >