운영체제(OS) - 4. Thread

Doyun Geum·2020년 1월 2일
1

Operating System

목록 보기
4/9

Basics

  • process 안에 최소 1개의 thread가 존재한다.
  • process 1번에 thread 1, 2가 있다. 이때 수행 단위는 thread
  • 각 thread별로 ID, PC, 레지스터, 스택(지역 변수가 들어가는)
  • code section, data section(전역 변수)와 OS가 가진 resource들을 공유한다.
  • 여러 가지 일들을 동시 다발적으로 할 때 일을 나눠서 할당하고자 할 때 쓰인다.
  • 프로세스를 쓰지 않는 이유는 ?
    fork()를 하여 프로세스가 생성되는데 이는 복제를 하는 것이라 하나만 생성하는 thread보다 무거울 수 밖에 없다.
  • code, data, files은 공유 / 스택과 레지스터는 개별로 관리한다.
  • context swtiching이 일어날 때 PCB에 pc register 값과 각 register를 저장해놓는다. (process to process)
  • PCB간 통째로 교환이 이루어지는게 process간 context switching이라면 PCB의 일부(thread)만 교환이 이루어지는게 thread간 context switching이다.
  • thread간 context switching이 일어날 때 같은 프로세스에 있더라도 register를 독립적으로 사용하기에 이 값을 저장하고 가져와야 한다.

멀티 스레드 프로그램

  • 하나의 프로그램을 2개 이상의 객체가 수행하는 것
  • multithread server 구조
    • 동시 다발적으로 일어나는 일들을 thread로 구현이 가능하다.
    • server data 분리
  • 장점
    • 지속 가능한 반응성
    • 자원 공유가 프로세스보다 효율적이다.
      • 프로세스간 공유는 메시지 패싱, shared memory는 외적인 공간을 써서 하지만 thread는 내부적으로 가능하다.
    • 경제성
      • 프로세스간 context switching 보다 가볍고 효율적 (PCB를 통째로 가져오고 빼는 작업이 생략될 수 있음)
    • 규모

Multicore programming

  • Data splitting

    • 동기화 문제를 방지하고자 적절히 나눠줘야 한다.

      Parallelism

    • multi core system에서 가능한 term이다.

    • 지금 바로 동시에 수행한다.

    • 그 시간에 하나의 task가 아닌 여러 tasks를 동시에 수행한다.

      Concurrency

    • 일정 시간에 tasks를 수행한다.

    • 하나의 core에서 말할 수 있다.

    • ex. 2ms간 무슨 일을 했어 ? t1, t2, t3 했어

  • Data parallelism

    • 동일 data를 각 core에 분배, 한쪽은 0 ~ N/2-1, 다른 족은 N/2 ~ N-1
  • Task parallelism

    • thread 역할을 분산
  • thread 개수가 증가할 수록 HW의 support가 필요하다.

    • core 하나에 여러 개의 thread를 지원하기 시작
    • 빠른 교환으로 하나의 core에 여러 개의 thread가 적재될 수 있다.
  • thread, process serial하게 수행하는 구간, parallel하게 수행하는 구간이 따로 있다.

    • thread 간에 data를 교환하는 작업이 serial하기에(완벽한 parallel이 아니기 때문에) core를 하나 더 들린다고 성능이 2배로 늘어나는 것은 아니다.
  • User thread

    • 보통 코드에서 만든 thread로 (응용된 thread) 이 thread를 kernel thread와의 relationship이 존재해야 user thread가 정상적으로 동작이 가능하다.
    • OS 입장에서는 자원이 한정적이기 때문에 kernel thread를 따로 만들어 관리하여 User thread와 연결될 때 User thread를 수행할 수 있도록 한다. 그래서 User thread를 엄청나게 만들어도 문제가 일어나지 않도록 한다.
  • Kernel thread

    • kernel 자체에서 제공, HW와 연결, User thread의 악용을 방지하기 위해, HW의 자원을 활용하기 위해 존재

Models

Many-to-One

  • User thread가 여러 개지만 하나의 kernel thread만 존재하여 이것에 접근하기 위해 경쟁하는 구도이다.
  • 하나의 User thread가 kernel thread와 관계를 설정하고, 그 thread가 독점하는 문제가 생길 수 있다. multicore system이 있다고 하더라도 커널 thread가 하나이기에 병렬로 수행 불가능
  • kernel design이 상당히 쉽다.

One-to-One

  • User thread 하나당 kernel thread 하나
  • OS의 자원이 충분하다면 충분히 활용이 가능한 모델이다.
  • concurrrency 측면에서 상당히 좋다.
  • 각 User thread가 kernel thread를 여러 개를 원한다면 문제가 생길 수 있기에 User thread에 제한을 둬야 한다.
  • blocking이 일어나지 않는다.
  • 커널 스레드를 생성하는 overhead가 프로그램 성능을 저하시킬 수 있다.(각 user thread에 커널 스레드 생성해야 함)

Many-to-Many

  • kernel에서 조금 부담스러운 (User thread 만 개, kernl thread 500개) 환경에서 blocking 가능성은 적지만 Many-to-One처럼 어떤 User thread가 kernel thread와 관계를 설정할지 정해줘야 한다.
  • LWP가 각 kernel thread가 어떤 User thread와 연결할지 정해준다.

Two-level

  • Many to Many와 비슷하지만 응용 thread가 정말 중요하다고 생각되면 blocking 되지 않도록 하나의 kernel thread와 연결시켜 줄 수 있는 모델이다.

Thread는 어떻게 만들어질까 ?

  • thread libraries가 있어 여기서 thread를 생성하고 없앤다.
  • Pthread가 가장 대중적인 방식으로, API specification(어떤 것을 제공해줘야 하는지 명시됨)이다. 스레드를 구현한 것은 아님
  • 표면적으로 보이는 function의 이름만 통일되어 있고 내부적으로 thread가 구현되는건 OS마다 다르다.
  • pthread_create에서 thread를 만들고 thread가 끝나면 pthread_join(process의 wait과 유사)으로 오게끔 동작한다. 프로세스에 thread가 있기 때문에 thread의 자원은 프로세스로 다시 돌아가는게 일반적이다.
  • CPU가 실행시켜주는 thread는 kernel level thread이다.

Implicit threading

  • thread의 생성과 관리를 프로그래머가 아닌 컴파일러와 런타임 라이브러리에서 해준다.
  • thread의 생성과 관리를 시스템에서 해주고 application은 thread를 쉽게 활용할 수 있게 library에서 제공해준다.
  • explicit threading은 직접 thread를 생성하고 사용

Thread Pools

  • 프로세스를 시작할 때 Thread를 여러 개 만들어 놓고 필요할 때 할당해준다.
  • 이미 library가 Thread pool를 가지고 있고 thread를 사용할 때 생성이 아닌 thread pool에 접근해서 바로 사용이 가능하다. 사용 후 다시 thread pool로 돌아간다.
  • 하나의 application에서 사용할 수 있는 thread에 제한을 줄 수 있다.
  • 시스템(library)에서 생성과 관리를 담당하여 User는 task에 집중할 수 있다.
  • Grand Central Dispatch(GCD)
    • apple 스타일의 thread pool, 동적으로 thread pool 크기를 바꾸어 주는 구조 serial queue랑 concurrent queue가 존재
    • serial 순서대로 작업
    • 각 application은 main serial queue를 지니고 있다.
    • concurrent queue에 3개의 queue(low, default, high)가 있어 thread가 concurrency(동시에) 수행 되도록 한다.

fork() & exec()

  • thread만 복사 or process 통째로 복사 : OS마다 다르다.
  • exec()는 똑같이 동작

Signal handling

  • 어떤 process에게 어떤 일이 일어났음을 알려주는 것 : signaling
  • signal을 받는 입장에서는 signal handler가 있어야 한다.
    1. 특정 event에의해 시그널이 발생한다.
    2. 시그널이 프로세스에게 전달이 된다.
    3. 모든 시그널에 대해 default signal이 생성되어 있고(OS kernel이 만든다.) 이외에 User defined signal이 있다. 신호가 전달되면 반드시 처리해야함
  • 동기식 신호: 잘못된 메모리 접근, 0으로 나누기 등으로 신호가 발생하고 신호를 발생시킨 연산을 수행한 동일한 프로세스에게 전달된다.
  • 비동기식 신호: 실행 중인 프로세스 외부로부터 발생되면 비동기식으로 신호를 전달 받는다.(ctrl c 강제 종료 또는 타이머가 만료된 경우)
  • 모든 신호는 둘 중 하나의 처리기에 의해 처리
  1. Default signal 처리기
  • 모든 신호마다 커널이 실행시키는 default signal 처리기가 있다.
  1. User define signal 처리기
  • default 처리기를 대체할 수 있다.
  • thread가 여러 개, 각 signal handler가 존재할 때 어떤 스레드에게 signal이 가야 할까 ?
    • OS에 따라 다르다. 하나, 특정, 모든 스레드에게 갈 수 있다.
    • 왜 필요할까 ?
      • process 단위에서 사용되는 interrupt라고 보면 된다.
      • interrupt가 필요한 이유랑 같음

Thread Cancellation

  • thread가 끝나기 전에 강제 종료시키는 작업
  • target thread(없앨 thread)를 정한다.
  • process에서는 abort() system call을 호출하는 동시에 죽는다. thread는 이와는 다르다.
  • Asynchronous
    • 즉시 target thread를 없앤다.
  • Deferred (default)
    • 종료되어야 하는지 계속 체크를 하다가 종료되어야 한다고 할 때 종료되는 것
    • 왜 필요할까 ?
      • process는 아예 다른 메모리 공간에 생성, 다른 resource 할당되어 즉시 죽어도 문제가 없다.
      • 하지만 thread는 공유된 메모리 공간에 생성, resource 또한 공유되어 target thread에서 lock을 걸어놓은 변수가 있다면 이를 즉시 죽일 시에 문제가 발생한다. 이런 문제점을 방지하고자 사용된다.
      • critical section(중요 부분)에 target thread가 작업하고 있을 때 또한 공유 자원을 사용하고 있을 때 죽이게 되면 큰 문제가 발생한다.
      • testcancel을 체크한다.(cirtical section에 없을 때)
      • cancellation point, cancel해야 할 지점을 저장하는 point에 도달했을 때에만 종료되게 한다.(deferred에서)

Thread-Local Storage(TLS)

  • thread는 프로세스의 data(ex 전역변수)를 공유하는데, 이를 복사해서 따로 두는 것을 말한다. process 내에서의 전역변수가 아닌 thread 내에서의 전역변수이다.
  • thread 자신만이 접근할 수 있는 data를 가질 수 있게 한다. TLS가 그런 data를 말한다.
  • race condition: 하나의 공유하는 resource에 대해 여러 개의 thread가 경쟁을 하여 resource가 충돌되는 경우를 말한다.
    ex) i++은 assembly로 변환할 때 레지스테 옮기고 레지스터를 1 더하고 이 값을 다시 넣어주는 방식인데 CPU는 연달아 수행하지 않을 때 원하는 값이 나오지 않는 경우가 있다.
  • race condition을 막기 위해 thread간 따로 계산을 하고 마지막에 합치는 방식(TLS)을 사용한다.

LWP(Light Weight Process)

  • kernel thread에 존재하고(kernel thread 개수만큼 존재) 어떤 user thread와 연결해줄지 결정한다.
  • kernel이 LWP에게 user thread를 선택하거나 취소하라고 알려줘야 한다.(upcall)
  • User thread들을 위한 mini scheduler
profile
안녕하세요, 서버 개발자 도유니입니다.

0개의 댓글