[유혁 교수의 운영체제] 8. Thread

Thread

Review of Process

  • Process - abstraction for
    • Execution unit - 스케줄링 단위
      • 프로그램은 스케줄링의 단위가 될 수 없다. 하나의 프로그램 안에서 여러 개의 프로세스가 발생할 수 있기 때문이다.
    • Protection domain - 소유하고 있는 자원에 대한 보호
  • Program과 Process 관계
    • Executing program with a single thread of control
      • 지금까지의 프로세스 개념
        • 하나의 실행 흐름을 가지고 실행 중인 프로그램
    • 1 개의 실행 흐름을 여러 개로 만든다면? -> thread 개념의 시작
      • if 문과 같은 분기점도 실행 흐름을 여러개 갖는 것 같아 보일 수 있지만 X

Thread란 무엇인가?

  • 정의
  • Execution Unit
  • 프로세스 내의 실행 흐름
  • 프로세스보다 작은 단위(finer grain)
  • 프로세스와 같은 protection domain은 없음
    • 스레드가 서로 간의 데이터에 접근 가능
  • 동기
    • 하나의 프로세스에는 하나의 control만이 존재하기 때문에 한 번에 하나의 일만을 처리
      • Single Thread : 하나의 프로세스 안에는 하나의 core만 사용이 가능하므로 core가 여러개여도 성능 개선이 전혀 안되며, CPU자체의 clock 성능을 높이는 수 밖에 없다.
    • 프로세스에서 할 작업을 여러 개로 나눈 후 (Execution Flow가 여러개로 나눠짐)에 각각을 thread화 한다면 병렬적으로 작업을 완수할 수 있음
      • 이렇게 되면 한 개의 프로세스가 여러 개의 core를 동시에 사용할 수 있게 된다.
    • Cooperative process와의 차이점
      • Cooperative process는 서로 다른 Protection Domain이므로
        • 프로세스간 통신(IPC) 비용
        • 프로세스 간의 문맥 교환 비용
      • Process 내에서 cooperation하는 thread로 만든다면
        • Process보다 적은 비용으로 cooperative process가 하는 일을 동일하게 수행 가능

Thread와 CPU utilization

  • Thread의 수와 Throughput(처리량)

  • Thread의 수가 증가할수록 CPU의 utilization이 증가

    • 임계값을 넘어가면 다시 감소 -> thread switching 비용이 증가함
    • CPU(core를 의미)의 수가 많은 시스템일수록 thread를 이용하는 게 유리
      • Multi-processor, 즉 CPU의 수가 많을수록 한 process의 여러 thread를 parallel하게 실행

Process와 Thread

  • Process
    • 하나의 thread(실행 흐름)을 가짐
    • Process간의 memory는 독립적이므로, 서로의 영역을 접근 못함
    • Process간의 switching 비용이 큼 (상대적으로 heavyweight)
  • Thread
    • 하나의 프로세스 안에 여러 개의 thread가 존재
    • 프로세스의 code 영역과 data 영역은 thread간에 공유
    • thread들은 같은 memory 영역을 공유하여 thread간 switching 비용이 적음 (process switching보다 lightweight)
    • Called “lightweight process” (lwp)

Thread의 구성 요소

  • Thread도 실행에 관련된 자료 구조가 필요 (process는 PCB가 필요했던 것처럼)
    • Thread ID - thread 식별자
    • Program counter - 현재 실행중인 instruction의 주소
    • Register set - CPU의 레지스터 값들
    • Stack per thread (execution하는 동안의 임시 메모리)
  • 동일한 프로세스 내에 있는 thread가 공유하는 것
    • Code - 프로그램의 code section
    • Data - 프로세스의 data section
    • File - 프로세스에서 open한 file (이것은 디스크에 존재하는 file 그자체가 아니라 해당 file에 대해 임시적으로 생성된 커널 내 자료구조를 말한다.)
  • 위의 이유로 thread switching 비용이 적음

단일 thread와 multithreaded process

  • code, data와 files는 여러 개의 thread가 공유하되,
  • stack과 registers는 thread마다 각각 존재한다.

Myltithread Program의 장점

  • Responsiveness
    • 프로그램의 어느 부분을 담당하는 thread가 block되거나 시간이 걸리는 작업을 하더라도, 다른 thread들은 실행되고 있기 때문에 user의 입장에서는 그 프로그램이 interactive하다고 볼 수 있음
      • 다만, 여러 개의 thread가 read가 아닌 write를 하고 있을 때에는 consistance를 맞춰 덮어쓰기가 발생하지 않도록 해야한다. (동기화)
      • 이것은 read only인 code보다, read&write가 가능한 data에서 조심해야 한다.
    • Single Thread 프로세스는 block되면, 해당 프로세스는 아예 멈추고 실행이 다른 프로세스로 넘어가게 된다.
  • Resource sharing
    • Thread들간에는 프로세스의 memory와 다른 자원들을 공유
  • Economy
    • Thread들은 단일 프로세스 memory영역에서 실행되기 때문에 새로운 프로세스를 생성하는 것보다 thread를 생성하는 것이 비용이 적게 들어감
      • 같은 개수의 process를 만드는 것보다 같은 개수의 thread를 만드는 것이 더 비용이 적게 들어간다.
  • Scalability (core가 늘어날수록 성능이 늘어나는 능력)
    • 여러 개의 thread가 각각 다른 processor에서 동시에 실행 가능(parallelism)

Multicore Programming

  • 최근 프로세스 설계 동향은 하나의 chip에 여러 개의 computing core을 탑재 -> multicore processor
    • multithread programming에 아주 적합한 하드웨어
    • GPU 또한 요즘에는 core의 수가 4000개를 넘어서는 것도 있다.
  • Multithread programming은 multicore 시스템에서도 효율적
    • Muticore processor는 운영체제에서 각각의 core를 하나의 프로세서로 인식하고 스케줄링
    • 각각의 thread를 core에 할당하여 실행 가능
    • multicore는 cache를 공유하기 때문에, data, code 등 프로세스의 자원을 공유하는 multithreaded programming에 보다 효과적임
      • multi processor는 cache를 공유하지 않기 때문에 Cache coherency protocol이 별도로 필요하다. 즉, 여러개의 프로세서들이 같은 메모리를 동시에 쓰게 되면, 그 값을 invalidation을 해주는 과정이 계속 필요하다.

User and Kernel threads - User thread

  • Thread를 지원하는 주체에 따라 나뉨
    • User thread - User Space에 존재
    • Kernel thread - Kernel에 존재
  • User threads
    • 커널 영역 위에서 지원되며 일반적으로 user level의 라이브러리를 통해 구현됨
    • 라이브러리에서 thread를 생성, 스케줄링과 관련된 관리를 해줌
    • 장점
      • 동일한 메모리 영역에서 thread가 생성, 관리되므로 이에 대한 속도가 빠르다.
    • 단점
      • 여러 개의 user thread 중에서 하나의 thread가 system call의 요청으로 block이 된다면 나머지 모든 thread 역시 block된다.
        • Kernel은 여러 개의 user thread들을 하나의 process로 간주하기 때문
          • user thread에 대해 할당된 메모리는 kernel 공간에서 할당된 것이 아니다.

User and Kernel threads - Kernel thread

  • Kernel Thread
    • 운영체제에서 thread를 지원
    • 커널 영역에서 thread의 생성, 스케줄링 등을 관리
    • user level의 multithread가 가능하려면, kernel 안에서의 multithread가 먼저 가능해야 한다.
      • user level의 thread는 kernel 입장에서 하나의 프로세스일 뿐이므로 정말로 여러개의 하드웨어(core)를 동시에 사용하는 효과를 내려면, kernel 공간에서 그만큼의 thread가 필요한 것이다.
        • 실제로 user program이 여러 개의 core를 사용하려면 여러 개의 kernel thread가 이 프로그램에 할당되어있어야 한다.
    • 장점
      • Thread가 system call을 호출하여 block이 되면, kernel은 다른 thread를 실행함으로써 전체적인 thread blocking이 없음
      • Multi(core) processor 환경에서 커널이 여러 개의 thread를 서로 다른 core에 할당할 수 있음
    • 단점
      • User thread보다 생성 및 관리가 느림

Q : Single Core에서 여러 개의 thread가 존재한다면 한 순간 t에서 보았을 때, 여러 개가 동시에 실행되고 있는건가요? 프로세스처럼 하나만 실행되고 있는건가요? A : core는 하나이므로 하나의 thread만 동작하고 있는 것이다.

Q : 그러면 프로세스와 다른 점은 메모리 공간이 공유가 가능하다는 점에서 장점을 가지는건가요? A : 그렇다. 추가적으로 switching 비용도 적다. context switching을 하려면 status를 저장/로딩해야 하는데 code, data, file은 기본적으로 동일하기 때문에, switching할 때 저장해야할 것들은 registers와 stack 뿐이다.

Q : kernel이 user thread를 구별 못한다고 하셨는데 시스템콜을 요청한게 프로세스인지 스레드인지는 구별하는건가요? 구별한다면 어떤 프로세스의 스레드인지는 구별하나요? A : 좀 더 구체적으로 설명하자면, 프로세스가 하나 존재한다면, 이미 그 프로세스에 대해서 Kernel thread는 하나가 할당이 되어있다는 뜻이다. 즉 하나의 context에 대하여 User space 관점에서는 여러 개의 thread가 돌아갈지 언정, Kernel 관점에서는 프로세스에 대하여 한 개의 Kernel thread만 할당되었다고 볼 수 있다. 이러한 상황에서는 Kernel은 User space에서 움직이는 것이 thread인지 process인지 구분을 못한다.

Mapping of User & Kernel Thread : Many to One

  • Many-to-One
    • Thread 관리는 user level에서 이루어짐
    • 여러 개의 user level thread들이 하나의 kernel thread로 매핑됨
    • kernel thread를 지원하지 못하는 시스템에서 사용됨
    • 한계점
      • 한번에 하나의 thread만 커널에 접근 가능
      • 하나의 thread가 커널에 접근(calling system call)하면 나머지 thread들은 대기해야함
      • 진정한 concurrency는 지원하지 못함
      • 동시에 여러 개의 thread는 하나의 process이기 때문에 multiprocessor(core)이더라도 여러 개의 precessor(core)에서 동시에 수행 불가능

커널 스레드를 지원하는 커널에서는 커널이 multithread화 될 때 프레임 쉘이 발생한다. 프레임 쉘이 발생하면 모든 status를 저장해야 한다.

Mapping of User & Kernel Thread : One to One

  • One-to-One

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

Mapping of User & Kernel Thread : Many to Many

  • Many-to-Many
    • 여러 개의 user-thread를 여러 개의 kernel thread로 매핑
      • 프로세스에 얼마 만큼의 하드웨어 자원(프로세서/코어)을 할당하는지 여부에 따라 달라짐
    • Many-to-One과 One-to-One model의 문제점을 해결
    • Kernel thread는 생성된 user thread와 같거나 적은 수 만큼만 생성이 되어 적절히 스케줄링
      • One-to-One처럼 사용할 thread의 수에 대해 고민할 필요 없음
        • kernel thread가 user thread보다 적더라도 스케줄링을 통해 수행 가능하므로
      • Many-to-One처럼 thread가 system call을 사용할 경우, 다른 thread들이 block되는 현상에 대해 걱정할 필요 없음
    • 커널은 적절히 user thread와 kernel thread 매핑을 조절하여 위와 같은 장점을 보장할 수 있음

  • user thread에 대한 라이브러리가 적절히 user thread들을 multiplexing 해줌

Q : 그러면 many-to-many 방식을 채택하면 커널이 쓰레드 수를 조절하기 때문에 4페이지에 나오는 그래프처럼 쓰레드 수 증가에 따라 throughput이 감소하는 현상이 발생하지 않게 되나요? A : Thread와 Throughput 상관 그래프에서 thread는 정확히는 kernel thread의 수를 말한다. user thread는 개수가 늘어나도 성능증가가 core 개수만큼 나타나지 않는다. user thread는 어차피 하나의 core밖에 사용할 수 없기 때문이다.

Thread로 인한 운영체제의 변화

  • 프로세스 기반의 운영체제 시스템 콜
    • 프로세스 관련 시스템 콜 semantics는 프로세스 기준으로 작성됨
      • Thread의 개념에 대한 고려가 필요
      • fork(), exec()
        • Thread 지원을 위해서는 변화가 필요
      • Thread 종료 문제
        • Multi-thread일 경우 함께 일하는 thread의 종료는 프로세스보다 복잡해짐
          • Thread cancellation issue
  • Multi-threaded programming에 대한 지원
    • Thread 스케줄링
    • Thread 간 통신 방법
    • Thread가 사용할 메모리 공간의 할당
      • Stack, thread specific data

Threading Issues: Creation

  • The fork and exec system call
    • Multithreaded program에서의 fork와 exec의 의미는 달라져야 함
    • fork
      • 하나의 프로그램 내의 thread가 fork를 호출하면 모든 thread를 가지고 있는 프로세스를 만들 것인지 아니면 fork를 요청한 thread만을 복사한 프로세스를 만들 것인가의 문제
        • Linux에서는 2가지 버전의 fork를 만들어 각각의 경우를 처리하도록 함 (상황에 따라 필요한 fork 버전이 다르다.)
          • fork1 - fork를 요청한 thread만 복사
          • fork - 전체 thread 복사
    • exec
      • fork를 하여 모든 thread를 복사 했을 경우 exec을 수행하면 모든 thread들은 새로운 program으로 교체가 됨
        • 교체될 thread들의 복사는 불필요한 작업
      • fork를 하고 exec을 수행할 경우 fork를 요청한 thread만이 복사되는 것이 더 바람직함
      • 그러나 fork를 하고 exec을 수행하지 않는 경우에는 모든 thread의 복사가 필요하기도 함

Threading Issues: Cancellation

  • Cancellation Thread cancellation : thread의 작업이 끝나기 전에 외부에서 작업을 중 지시키는 것
  • 하나의 thread에서 중지 명령이 결국은 다른 thread의 모든 작업을 중지 시켜야 하는 경우
    • Ex) 웹 브라우저에서 이미지를 읽어 오는 과정에 사용자가 stop 버튼을 눌러 취소할 경우 이미지를 읽어오는 thread 역시 중지가 되어야 함
  • 자원이 thread에게 할당된 경우 cancellation의 문제
    • System의 자원을 사용하고 있는 thread가 중지 될 경우 할당된 자원을 함부로 해제 할 수 없다.
      • 다른 thread가 그 자원을 사용하고 있을지도 모르기 때문

Threading Issues: Thread pools

  • Thread pools
    • Thread가 자주 생성되고 제거되는 상황에서 새로이 thread를 만드는 시간이 실제 thread가 동작하는 시간보다 긴 경우가 있음
    • Thread는 system의 자원이기 때문에, system의 동작을 보장하는 최대 한의 thread 수에 대한 제한이 필요
    • 해결 방안
      • Thread pool을 만들어 프로세스가 새로이 실행 할 때, 정해진 수만 큼의 thread를 만든 후 pool에 할당
      • 새로운 thread가 필요하면 pool에서 가져오고, 작업이 끝나면 그 thread를 제거하지 않고 다시 pool에 넣어둠
    • Pool을 통해서 제한된 수의 thread를 관리하면, 그러지 않은 경우 보다 thread의 생성에 걸리는 시간이 적음
    • 전체적인 system에서 생성할 수 있는 thread의 수가 제한되어 있기 때 문에, 너무 많은 thread의 생성에 따른 system의 부하를 줄일 수 있음
    • Pool에서 관리할 thread의 수는 system의 memory나 예상되는 작업의 수를 통해서 휴리스틱하게 결정
      • provisioning : 프로세스에 하드웨어를 할당하는 것을 말하며, 보통은 리퀘스트가 가장 많이 들어올 때를 대비해서 하드웨어를 맥시멈으로 할당한다.

Threading Issues: 스레드간 IPC

  • Multithreaded 프로그래밍에서는 스레드 간 통신이 필요
  • 스레드 간 IPC의 구현은?
    • 동일한 프로세스 내의 스레드 간 통신은, 공유 메모리가 효율적
      • 이유 – thread들은 같은 프로세스의 data 영역을 공유하므로, 자연스러운 공유 메모리가 가능해짐
  • 결국 IPC가 최소화
    • Thread들 간의 자원 공유로 인하여 가능해짐
    • 그렇지만 특정한 경우에는 공유 메모리로 안되는 IPC가 있을 수 있으며 이 때에는 Socket과 같은 다른 방식의 IPC가 필요함
  • 만일 다른 프로세스 내에 존재한 스레드와의 통신은?
    • 프로세스의 경우와 비슷한 성능을 보임
    • 이러한 통신이 빈번하다면, 프로그램 설계의 잘못

Implementation

  • Thread library
  • POSIX Pthread: interoperability(내가 만든 프로그램이 다른 곳에서도 작동됨)를 위한 표준
    • IEEE 1003.1c: pthread_create()
  • Windows threads API
    • Via system call: CreateThread()
  • Linux threads
    • Introduced in version 2.2
    • Via system call: clone()
  • Java threads
    • Thread class

리눅스에서 thread는

  • Thread is hard to support !
    • Also thread programming is difficult
    • 스레드 간의 동기화와 디버깅의 까다로움
  • Thread implemented as process
    • simply share resources(code, data, file) like threads
    • different from Windows and other Unix-descendants
    • discourage the use of threads…
    • thread API 대신 clone()을 사용
  • But kernel thread exist
    • process solely in kernel-space
    • schedulable and preemptable
      • interrupt가 가능하도록 하기 위함
      • 원래는 interrupt를 처리할 때 커널에서는 그것만 수행했지만 이제는 interrupt 처리 과정을 비롯한 다양한 수행들을 스케줄링하기 위해 커널 스레드가 사용된다.

Q : 요즘 컴퓨터들을 보면 cpu 성능에 8코어 16쓰레드 처럼 하나의 코어에 여러개의 쓰레드가 달려있는 경우가 있는데 여기서 설명한 쓰레드와 같은 의미인가요? A : 그것은 hyperthread를 말하는 것으로, 우리가 배우는 thread랑은 다른 개념이다.

Q : 그렇다면 linux에서는 kernel thread와 user thread간의 mapping이 필요가 없는건가요? A : 그렇다.

Q : 그럼 쓰레드가 독립적으로 동작하는 것이 아니라 프로세스 내에서 그룹으로 동작하는 것이라고 생각하면 될까요? A : 쓰레드는 독립적으로 동작하지만 하드웨어와의 매핑 과정에서는 동기화에 의해 서로에게 영향을 줄 수는 있다.

Q : 교수님 프로그램이 multi core를 지원한다는 것은 multi threading이 가능하다는 것으로 이해하면 될까요? A : 그렇다.

Q : library에서 user thread를 만들면 library에서 user thread를 kernel thread에 매핑하는게 맞나요? A : user library는 thread를 user space 내에서 스케줄링까지만 해줄 뿐이다. 즉, kernel이 스케줄링 해준 타임 퀀텀 내에서의 스케줄링만 해준다.

Q : multithread를 활용하는 process이더라도 underlying kernel이 mapping을 어떻게 하는지에 따라 다른 성능을 보여주는게 맞나요? A : 그렇다. 리눅스가 서버용 하드웨어로 많이 사용되는 이유 중 하나가, 리눅스가 multi core가 되면서 커널 thread 매핑에 따른 성능 개선이 좋았기 때문이다.

Comments