Operating System Ch12: Threads

Layfort·2023년 11월 8일
0

Operating System

목록 보기
1/7

Threads

1. Concept of threads

  • Virtualization Part에서 우리는 process라는 것을 배웠다.

    • Process는 CPU에 대한 virtualization!
    • 어떤 task가 마치 CPU를 독점적으로 사용하는 것처럼 보이게 한다.
  • 지금까지는 그래서... task와 process를 별도로 구분하지 않았다.

    • 한개의 process는 한개의 task를 수행한다!
  • 그렇지만 개념적으로 process는 task의 virtualization이 아니다!

    • CPU의 virtualization!!!
    • task라고 보이는 것은 현재 CPU의 state를 저장하기 위해서 program의 execution state도 저장하고 있을 필요가 있기 떄문에...
    • 그 과정에서 자연히 PC, SP등 program state를 가지고 있어서 task라고 보이는 것

1-1. What is thread?

  • Thread는 결국 프로그램의 흐름(Control flow)를 virtualization 하겠다는 것이다.
    • 단순히 task의 virtualization이라고 생각하자.
  • Process가 담당하던 수많은 virtualization 중에서, 현재 program의 execution state를 분리하여 virtualization하는 것이다.
    • registers, PC, SP(seperate stack)등(program state)
    • address space, file descriptor등은 공유된다.(task를 수행하기 위한 enviorment라고 생각하면 다소 이해가 쉽다.)

1-2. Process vs Thread

  • 위에서 말한것 처럼... process는 enviorment, thread는 task
    • process는 task(threads) + enviorment data(address space, PWD, open file descriptors)
      • enviorment는 공유된다.(한 공장에 노동자가 여러명이 있다고 하더라도 공장 내부 장비나 물품은 공유되지 않는가?)
    • thread는 개별 task의 진행상황을 나타낸다.
  • 그렇기 때문에... 지금까지는 process를 schedule하였지만, 지금부터는 scheduling의 단위는 thread로 돌아간다.

2. Threading Issue

2-1. fork()

  • 간단하지만 중요한 문제... → child process는 parent process의 thread도 복사해야 하는가?
    • Pthread에서는 현재 실행중인 thread만 복사한다.(나머지는 무시)
    • Unix는 기본적으로는 모두 복사이지만, 단일 thread 복사도 지원한다. : fork1()

2-2. exec()

  • 당연하지만, exec()가 실행되면, 현재 thread를 제외하고는 전부 terminate다.(애초에 새로시작이니까 이는 trivial하다.)

2-3. Thread Cancellation

  • thread에 kill signal을 보냈다면, 이를 언제 처리할 것인가?
  1. Asynchornous cancellation: 다른 thread에서 직접 삭제
    • 큰 문제가 있는데... 만일 삭제하고자 하는 thread가 lock을 잡고 있었다면?
    • 이 lock은 절대 풀리지 않을 것이다...
  2. Deferred cancellation: 즉시는 무리고... signal을 남겨서 적절한 시기에 자기가 스스로 terminate하도록 하자
    • 적절한 시기가 언제인가? → lock을 풀때?, lock을 잡으려 할때?, 구현마다 다를 것
  • 일반적인 thread API는 2가지를 전부 지원하긴 한다.

2-4. Signal Handling

  • 어느 thread로 signal을 보내야 하는가?
    • 특정 thread(최초의 thread라던가...)
    • 모든 thread
    • signal을 처리하는 특별한 thread를 만들기
  • POSIX에서는 일단 signal은 process가 받아오고, thread 별로 다른 block_mask를 허용한다.
    • 이유는 몰?루

2-5. Library

  • library도 자체적으로 현재 진행상황을 저장하는 경우가 있다.

    • e.g. errno, strtok등 이전에 호출된 상황을 저장하는 function이나 data가 있음
  • 일반적으로 library는 공유된다!

    • 그렇지만 thread는 다른 task기 때문에 다른 thread에서 불린 history는 그다지 궁금하지 않다...
  • 그래서 Multithread-safe(MT-safe) 기능을 가지는 library들이 나고고 있다.

    • thread 단위로 history를 기록하는 기능을 포함한다.
    • 즉 writable global data는 사용하지 않는다.

3. Implementing Threads

3-1. Kernel-level

  • OS가 thread를 만들어 준다!

    • 정확하게는 process관련 API들이 전부 system call로 구현되었던 것과 마찬가지로...
    • thread의 생성, 소멸, 관리, scheduling을 전부 kernel에서 수행하겠다.
  • OS가 process를 만들고 관리하던 것을 우리는 배웠다. 그것과 유사하게 thread 역시 그리하겠다는 것에 불과하다.

    • 이를 위해서 kernel은 PCB와 같은 thread table을 만들어서 thread를 관리한다.
    • Process에는 process당으로 할당되는 데이터를 저장하고
    • Thread에는 thread 단위로 저장되는 데이터를 저장하는데, 이때 process table entry에 대한 pointer를 통해서 process 데이터에 접근이 가능하도록 한다.
      • 또한 scheduling의 단위가 thread기 때문에... 그에 필요한 data(vruntime, nice)등의 정보도 thread 단위로 들어가게 된다.
    • scheduling은 thread table을 보고 결정한다.
  • 대부분의 OS가 이렇게 한다.

    • 가장 중요한 것은... 안전하다는 점이다.
    • 또한, 실질적으로 timeslice를 thread가 많을수록 더 많이 확보할 수 있다.
    • 그러나 결국 system call이기 때문에, overhead를 무시할 수는 없다.

3-1-1. Limitation

  1. system call은 무겁다. (fork()같은 process syscall에 비해서는 낫지만, 그래도 여전히 비싸다...)
  2. kernel이 모든 관리를 해야한다. + kernel에 데이터를 저장해야 한다.
    • kernel의 데이터는 한정되어 있으니까, 결국 최대 thread의 갯수는 한정적이게 된다.
    • 어떤 thread가 active thread인지 알 수가 없다. (scheduler는 그런거 고려 안함)
      • 간단히 말해서... thread를 프로그래밍 방법론적으로 사용하는 경우가 종종있다.
      • 이 경우 active thread와 sleep thread가 별도로 존재하는데,
      • 당연히 sleep thread로 scheduling 되는 것은 좋지 않다! 하지만 kernel은 이를 구별할 방법이 없다.
  3. OS dependency가 있다. 지원안하는 OS에서는 동작하지 않는다.

3-2. User-level

  • user code상에서(process 별로) thread를 mangement하는 일종의 custom kernel을 만들어서 사용하겠다.
    • 일종의 library같은 것(OS independent)
    • user level에서 관리되는 것이기에, OS는 thread의 존재를 알 수 없다.
  • 생성, 소멸, 심지어는 scheduling도 user level에서 알아서 다한다!
    • syscall이 아니라 그런 부분에서는 overhead가 없어 훨씬 빠르긴 하다.

3-2-1. Limitation

  • OS invisible이 결국 문제다.
    • OS는 thread가 있는지 없는지 모르기 때문에, time slice 배분에 이를 고려하지 않는다.
      • thread가 2개 있는 process든, 100개 있는 process든 time slice가 비슷하게 배분될 수 있다.
      • 그래서 실질적으로 더 많이 실행되고 이런건 없을 수 있음
    • 한개의 thread가 I/O등의 이유로 block되면 모든 thread가 다막힘(OS는 thread 별로 구분을 못하기 때문에)
    • 마찬가지로... lock에서도 비슷한 문제가 발생한다.
  • Multi-core를 활용할 수는 없다.(process에 완전 귀속되어있어서...)

4. Threading Model

  • 공부중, 잘 몰라요...

4.1. One to One

4.2. One to Many

4.3. Many to Many

5. OSTEPS reading

5-1. Terminology

  1. a race condition: the results depend on the timing execution of the code.
    -> we call this result indeterminate* where it is not known what the output will be and it is indeed likely to be different across runs.

  2. Because multiple threads executing this code can result in a race condition, we call this code a critical section.
    -> A critical section is a piece of code that accesses a shared variable (or more generally, a shared resource) and must not be concurrently executed by more than one thread.

  3. mutual exclusion: guarantees that if one thread is executing within the critical section, the others will be prevented from doing so.

  4. Sometimes, the grouping of many actions into a single atomic action is called a transaction.

5-2. Thread vs Process

  • 개별 thread는 process와 유사해보이지만, 그래도 same address space를 공유한다는 결정적인 차이가 존재한다.
  • 그 이외의 PC, Reigster state는 분리되어야 한다...(stack도 thread마다 잘라서 사용한다.(아마도?))
profile
물리와 컴퓨터

0개의 댓글