본문 바로가기

컴퓨터 공학 전공/운영체제

Thread

728x90

Thread가 떠오른 배경

  • 프로세스는 너무 무겁다
    • 각각의 프로세스는 address space를 가져서 code와 data를 따로 차지한다
    • OS 자원과 정보
    • Hardware State
  • 프로세스를 구성하기 위한 공간과 data structure가 필요하기 때문에 새로운 프로세스를 생성하는 비용이 너무 비싸다
  • OS를 거쳐서 IPC를 하는 비용이 너무 비싸다

위와 같은 치명적인 이유들로 Thread가 부상되었고  다음의 특징을 가진다

  • code와 data 영역을 공유한다
  • 같은 권한을 가진다
  • 자원들을 공유한다
  • 자신만의 hardware state를 가진다

프로세스가 모든 자원과 공간을 독립적으로 가지고 있는 개체라고 한다면 스레드는 공통된 부분은 공유하고 필요한 부분만 독립적으로 구성되어있다.

 

Process vs. Thread

  • 스레드는 하나의 프로세스 안에 존재한다
  • 프로세스는 여러 개의 스레드를 가질 수 있다
  • 스레드는 같은 address space를 가지기 때문에 sharing data가 싸다
  • 최근에 프로세스는 스레드를 담기 위한 컨테이너의 역할을 주로 수행한다
  • 프로세스에 비해 스레드가 더 dynamic 하다

이런 점들은 throughput, responsiveness, resource sharing, economy, utilization 같은 부분들에서 훨씬 뛰어나기 때문에 이제는 thread를 통해 multiprogramming을 하는 경우가 대부분이다.

 

Pthread

이제 threads interface 중 가장 대표적인 POSIX Thread에 대해 살펴보려고 한다.

  • Thread creation / termination
    • #include <pthread.h>
    • gcc library libpthread.a : -lpthread
  • int pthread_create (pthread_t *tid, pthread_attr_t *attr, void *(start_routine)(void *), void *arg)
    • tid: thread의 id로 OS가 부여한다
    • *attr: pthread의 속성을 설정하는 것으로 default는 NULL이다
    • (void *): 수행할 함수
    • *arg: 함수에 전달할 인자
  • void pthread_exit (void *retval)
    • pthread를 종료한다
  • int pthread_join (pthread_t tid, void **thread_return)
    • parent thread가 child thread가 종료되기를 기다리는 함수로 프로세스의 waitpid 함수와 같은 기능

 

Threading Issues

  1. fork() & exec()
    • 만약 하나의 프로세스에 여러 개의 thread들이 동작하고 있다고 가정해보자. fork() 시 프로세스 안에 있는 모든 thread들이 복제되는 것일까?
    • Pthread에서는 fork()를 호출하는 thread만 복제된다
    • Unix standard에서 fork()은 프로세스 안에 있는 모든 thread를 복제한다
    • Unix standard에서 fork1()은 fork()를 호출하는 thread만 복제된다
    • 일반적으로 exec()은 프로세스 전체를 대체한다
  2. Thread cancellation
    • Asynchronous cancellation
      • target thread를 즉시 종료한다
      • 만약 target thread가 자원을 가지고 있을 시 문제가 발생할 수도 있다
    • Deferred cancellation (default)
      • target thread가 바로 종료되지 않는다
      • pthread_testcancel()을 통해 thread 스스로 cancel 되어야 하는지를 주기적으로 check 하여 종료되어야 한다고 확인 시 종료된다
    • Cancellation APIs
      • int pthread_cancel (pthread_t thread): return 0, error시 return -1
      • int pthread_setcancelstate (int state, int *oldstate): cancel 허용 여부를 결정한다. oldstate는 이전의 state로 복구를 위해 필요하다
      • int pthread_canceltype (int type, int *oldtype): deferred, asynchronous 같이 cancel의 타입을 결정한다
  3. Signal handling
    • signal이 왔을 때 어디에서 받을 것인가에 대한 문제이다. 프로세스 안의 모든 thread, 특정 thread, signal을 요청한 thread 등 여러 가지 방법이 있지만 일반적으로 signal에 unblock인 thread 중 하나가 받는다.
  4. Thread-Local Storage
    • 프로세스 안의 thread들이 프로세스의 데이터를 공유하는 영역
    • TLS는 thread마다 자신만의 데이터 copy를 가질 수 있도록 한다
    • 지역변수와의 차이점은 지역변수는 하나의 함수 안에서만 보이지만 TLS는 해당 thread 안에서는 다 보인다는 점에서 static 변수와 유사하다
  5. Using libraries
    • errno: 오류 코드의 매크로 번호
  6. Easy sharing but error-prone
    • 전역 변수는 thread들 간에 쉽게 공유되지만 명시적이 아닌 묵시적으로 동기화를 할 시 error가 발생할 수 있다. 또한, 포인터처럼 주소를 넘겨주는 작업은 caller와 callee 사이를 넘어오면서 주소 값이 변할 수 있기 때문에 바람직하지 않다

 

Kernel-level Threads

  • OS가 관리하는 thread
    • OS가 thread와 프로세스를 모두 관리한다
    • 모든 thread의 실행은 kernel의 제어를 받는다
    • OS가 시스템의 모든 thread들의 스케줄링을 담당한다
      • 만약, 어느 프로세스 안의 하나의 thread가 block 상태로 빠진다면 OS가 이를 알아채고 그 프로세스 안의 다른 thread를 실행시킬 수 있다
      • I/O와 CPU의 overlap이 가능하다
    • kernel을 거치지만 그럼에도 프로세스보다는 싸다
    • Windows, Mac OS, Linux 등 대부분의 OS가 이 방식을 채택
  • 한계
    • 여전히 비싼 편에 속한다
      • 높은 수준의 병행성을 요구할 시, 더 적은 비용의 thread가 요구된다
      • thread operation을 함수 호출만큼 빠르게 할 수 없다
    • Thread operation이 모두 system call이다
      • thread operation을 할 때마다 protection boundary를 넘어가야 되고 이것은 같은 프로세스 내의 thread라도 마찬가지여서 비효율적이다
    • thread마다 kernel state를 유지시켜야 하므로 동시에 위치시킬 수 있는 thread의 수가 제한적이다 (~ 1000)
    • kernel-level thread는 프로그래머, 언어, 시스템에 모두 지원을 받을 수 있어야 한다

 

User-level Threads

  • 동기
    • thread를 더 값싸고 빠르게 커널의 도움 없이 user level 선에서 실행시키기 위해
    • library 수준에서 처리되므로 이식성을 높이고자
  • 장점
    • 각각의 thread는 PC, registers, stack, TCB (Thread Control Block)에 의해 간단히 표시된다
    • thread를 생성, switching, 동기화시키는 것이 함수 호출 수준으로 동작한다
    • kernel-level thread에 비해 10~100배 빠르다
  • 한계
    • OS에 보이지 않는다
      • OS와 합이 잘 맞지 않는다
    • OS가 잘못된 결정을 할 가능성이 높아진다
      • idle 상태에 있는 thread들을 스케줄링할 수 있다
      • 프로세스 안에 ready 상태의 다른 thread가 존재함에도 하나의 thread가 block 상태라는 이유로 프로세스 전체가 block 된다
      • lock을 소유하고 있는 thread를 unscheduling 시키지 않을 수 있다
      • 위의 경우들은 모두 OS가 user-level thread를 볼 수 없기 때문에 발생한다
      • 이를 해결하기 위해서는 kernel과 user-level thread manager (library) 간의 협력이 요구된다

'컴퓨터 공학 전공 > 운영체제' 카테고리의 다른 글

Process Synchronization (1)  (0) 2020.04.02
CPU Scheduling  (0) 2020.04.02
Process  (0) 2020.04.01