운영체제

Thread

Seonyoung Jeong 2021. 9. 11. 18:00

Thread란?

  • 프로세스 내의 실행 흐름이며, 프로세스보다 작은 실행 단위
  • Protection domain은 없다. 그래서 Process안에 Thread A, B가 있으면, A가 B의 내용을 읽을 수 있음
  • Thread의 구성 요소 : Thread ID (thread 식별자), Program counter (현재 실행중인 instruction 주소), Register set (CPU)의 레지스터 값들), Stack per thread (temporary한 메모리)
  • 단일 thread와 multithreaded process
    그림에서 보다시피 multithread 프로세스에서 프로그램의 code, 프로그램의 data, 프로세스에서 open한 file을 공유한다. 그렇기 때문에 thread switching 비용이 적다. 

Thread가 주는 이점

하나의 프로세스에는 하나의 control만 존재한다. 즉, 하나의 core만 실행하므로 한 번에 하나의 일만을 처리할 수 있다.

프로세스에서 할 작업을 여러 개로 나눠, 프로세스 내 각각의 thread에 작업을 부여한다면 병렬적으로 작업을 완수할 수 있다.

또한 Cooperative process는 protection domain을 제공하기 때문에, 프로세스간의 통신을 할 때 통신 비용이 많이 든다. 만약 Process 내에 cooperation하는 thread를 만든다면 Process보다 적은 비용으로 cooperative process가 하는 일을 동일하게 수행할 수 있다.

 

Thread와 CPU Utilization의 관계

그래프를 보면, Thread의 수가 증가함에 따라 Throughput(CPU Utilization)이 증가하고, 임계값을 넘어가면 다시 감소한다. CPU의 수가 많은 시스템일수록 thread를 이용하는 것이 유리하다. 그 이유는 Multi-processor와 같이  CPU의 수가 많을수록 여러 thread를 병렬적으로 실행할 수 있기 때문이다.

 

Process와 Thread의 차이

Process Thread
하나의 thread를 가짐 하나의 프로세스 안에 여러개의 thread
Protection domain으로 서로의 영역에 접근하지 못한다. Protection domain 기능을 제공하지 않기 때문에 thread 간에 접근이 가능하며, 프로세스의 code영역과 data영역을 공유한다.
Protection domain으로 인해 switching 비용이 크다. thread간 메모리 영역을 공유하기 때문에 switching 비용이 적다.

Multithread Program의 장점

Responsiveness 프로그램의 한 thread가 block되거나 시간이 걸리는 작업을 하더라도,  다른 thread들이 실행되고 있기 때문에 user 입장에서는 프로그램이 interactive하다고 느낌.
Resource Sharing Thread들간에는 프로세스의 memory와 다른 자원들을 공유함.
Economy 새로운 프로세스를 생성하는 것보다 thread를 생성하는 것이 비용이 적게 들어감.
Scalability 여러개의 thread가 각각 다른 processor에서 동시에 실행 가능함.

 

Multicore Programming

  • 최근 프로세스 설계 동향
  • Multicore processor는 운영체제에서 각각의 core를 하나의 프로세서로 인식하고 스케줄링한다.
  • multicore는 cache를 공유하기 때문에 multithreaded programming에 보다 효과적이다.
    (<-> multi process는 각각의 cache가 다르다. 그렇기 때문에 여러 process가 동시에 실행되면 Cache coherency protocol이 실행된다.)

 

Thread의 종류

1. User thread

  • User 영역에서만 돌아가는 thread이며, user level의 라이브러리를 통해 구현된다.
    라이브러리에서 Thread를 생성하고 스케줄링과 관련된 관리를 한다.
  • 장점 : 동일한 메모리 영역에서 thread가 생성, 관리되므로 속도가 빠르다.
  • 단점 : 여러개의 user thread 중 하나의 thread가 system call 요청으로 block된다면 나머지 모든 thread 역시 block이 된다.
    - Kernel은 여러 개의 user thread들을 하나의 process로 간주하기 때문!!
      - kernel은 PCB만 봐서 user thread가 여러개인지 애초에 인식을 못함.

2. Kernel thread

  • 운영체제에서 지원하며, 커널 영역에서 thread 생성, 스케줄링 등을 관리한다.
  • 장점
    - Thread가 system call을 호출하여 블록되면, 다른 쓰레드를 실행하기 때문에 전체적인 thread blocking이 없다.
    - Multiprocessor 환경에서 여러 개의 thread를 다른 프로세서에 할당할 수 있다.
  • 단점 : User Thread보다 생성 및 관리가 느리다.

 

User Thread와 Kernel Thread의 Mapping 방식

1. Many-to-One

  • 여러 개의 user level thread들이 하나의 kernel threaed로 매핑된다.
    **쉬운 이해를 위해 kernel thread라고 한것! multithread 프로그램 실행 시에 thread library가 여러 개의 thread를 위와 같이 스케줄링한다는 것을 보여주는 것!
  • Kernel thread를 지원하지 못하는 옛날 시스템에서 사용되었다.
  • 한계점
    - 한번에 하나의 thread만 커널에 접근이 가능하기 때문에 나머지 thread들은 대기해야 한다.
    - 동시에 여러 개의 thread가 system call 사용하는 것은 불가하다.
    - kernel thread는 하나의 cpu에만 접근이 가능하기 때문에 multiprocessor여도 여러 개의 processor에서 동시에
      수행하는 것은 불가능하다.

2. One-to-One

  • user thread가 생성되면 그에 따른 kernel thread가 생성되고 매핑된다.
  • 1:1이기 때문에 many-to-one 방법에서 문제되었던 system call 호출 시 다른 thread들이 block되는 문제를 해결할 수 있다.
  • 여러 개의 thread를 multiprocessor에서 동시적으로 수행할 수 있다.
  • 한계점
    - kernel thread를 무한대로 생성할 수 없으므로, thread를 생성, 사용하려 할 때 개수에 대한 고려가 필요하다.

 

3. Many-to-Many

  • many-to-one 모델과 one-to-one 모델의 문제점을 해결할 수 있다.
  • kernel thread는 생성된 user thread와 같거나 적은 수 만큼만 생성 가능하고, 이를 이용하여 적절한 스케줄링이 이러어진다.
    ==> thread의 수에 대해 고민할 필요가 없고, thread block 현상에 대해 걱정할 필요가 없다..
  • Thread library가 multi-plexing을 해준다.

 

Thread Issues

Fork, Exec, Cancellation Issue

  1. Fork() & Exec()
    - 프로그램 내의 thread가 fork()를 호출할 경우, 그 thread만 복사할 것인지, 프로그램 내의 모든 thread를 복사할 것인지에 관한 문제
    - 리눅스에서는 fork(), fork1()으로 구분하여 사용하고 있다.

    위와 같이 fork를 구분한 이유!

    Case 1) fork를 하고 exec를 하는 경우 전체 thread를 복사할 필요가 없다. 왜냐하면 exec()를 실행하게 되면 어차피 새로운 프로그램으로 대체될 것이기 때문에 굳이 thread 전체를 복사하는 것은 시간 낭비이다. 이러한 경우 fork를 요청한 thread만 복사하는 것이 바람직하다.
    Case 2) 그러나 fork를 하고 exec를 수행하지 않는 경우 모든 thread의 복사가 필요한 때도 있다.

    케이스마다 fork를 효율적으로 쓸 수 있는 방법이 다르기 때문에 구분함!!

     
  2. Cancellation
    - Thread Cancellation : Thread의 작업이 끝나기 전에 외부에서 작업을 중지시키는 것
    - 여러 개의 thread가 모두 cancel 되어야하는 경우가 존재한다.
       Ex) 만약 웹 브라우저에서 이미지를 읽어오는 과정에서 사용자가 stop 버튼을 눌러 취소하면, 웹 브라우저
            내의 thread 뿐만 아니라 이미지를 읽어오는 thread도 중지가 되어야 한다.
    - 근데 만약 이미지를 읽어오는 thread가 system의 자원을 사용하고 있다면 함부로 해제할 수 없다는 문제가 있다
    - 그래서 Cancellation은 두가지 방법이 있다.
      1) Asynchronous cancellation : target thread를 즉시 종료시킴.
      2) Deferred cancellation : 주기적으로 종료되어야 하는지 target thread를 확인함.

Thread pools

  • Thread pool을 만들어 프로세스가 새로 실행될 때, 정해진 수만큼의 thread를 만든 후 pool에 할당한다.
  • 새로운 thread가 필요하면 pool에서 가져오고, 작업이 끝나면 그 thread를 pool에 반납한다.
  • 위와 같이 pool을 사용하면, thread 생성에 걸리는 시간을 줄일 수 있고, 너무 많은 thread 생성으로 인해 오는 system 과부하를 줄일 수 있다.
  • Ex) Web server
    request가 peak로 들어올 때를 대비해 하드웨어를 최대로 준비하고 제공하는 것은 비용면으로 비효율적이다.
    Pool에 미리 100개 만들어 놓고 request가 들어오면 pool에서 thread를 끌어오는 것이 더 효율적이다.

 

Thread 간 IPC

  • Multithreaded 프로그래밍에서는 스레드 간의 통신이 필요하다. 이 경우에는 공유 메모리가 효율적이다. 공유 메모리를 통해 IPC를 최소화할 수 있다.
  • 만약 다른 프로세스 내에 존재한 스레드와의 통신이 필요한 경우 프로세스와 비슷한 수준의 비용이 필요하다. 이러한 통신이 빈번하다면 그건 프로그램 설계가 잘못된 것이다,,,,!!!!!!!!