일기 대신 코드 슬쩍

[운영체제] Threads & Concurrency (2) 본문

OS

[운영체제] Threads & Concurrency (2)

코코자 2024. 4. 18. 16:04

이어서 treads에 대해 배워보자.


4.5 Implicit Threading

implicit threading에 대해 알아보자.

Implicit threading : compiler와 run-time libraries에 의해 thread를 관리하는 것 (프로그래머가 직접 관리하는 것이 아님!!)

implicit threading을 쓴다면,,,,

  • programmer는 parallel하게 실행될 수 있는 task를 찾아야 한다.
  • programmer는 task를 function으로 코딩한다.
  • 그러면 run-time libraries가 thread 생성 관리에 대한 디테일한 것들은 알아서 해줌

→ 정리해보면, programmer는 parallel 한 task를 찾고 함수로 쓰기만 하면 나머지는 알아서 해주니까 편리하다!

근데 다음과 같은 issue가 존재한다.

1) thread를 생성하고 작업이 끝난 뒤 discard할 때 시간이 소요됨

2) thread수에 제한을 두지 않으면 system resource를 전부 차지할 수도 있음

방법에 따라 이 issue를 해결할 수 있다.

그렇다면 Implicit threading을 위해 이용하는 여러가지 방법들에 대해 알아보도록 하자.

1. Thread Pools

  • system 시작할 떄 미리 정해놓은 수의 thread를 생성하고, thread pool에 넣어놓는 것
  • request가 들어오면 thread pool에 있는 thread가 처리한다.
  • 만약 idle thread가 사용할 수 없는 상태이면, request는 queue에서 대기한다.
  • asynchronous execution에서 잘 작동한다.

그렇다면 thread pool의 장점은 뭘까

  • 빠르다 (thread가 이미 존재하기 떄문에)
  • 한정된 수의 thread에 대해 유동적으로 수를 늘려 사용한다.
  • (thread pool에 의해) 생성 과 실행 방식을 각각 분리시켜 시스템을 유연하고 효과적으로 이용할 수 있다!

2. Fork Join model

  • threads인데 process처럼 취급받음
  • = synchronous model as explicit thread creationn
  • = synchronous cersion of thread pools (implicit)

3. OpenMP

  • C, C++ or Fortran으로 작성한 API임
  • set of compile directives
  • 공유 메모리 환경에서 병렬처리 지원한다.
  • 병렬처리할 수 있는 code block 찾아내서 해당 부분 병렬처리할 수 있게 도와준다.
  • 병렬가능한 지역을 지정하기 위해 compiler directives를 집어넣는다.
  • 병렬 수준을 지정함(default: core수)
  • thread사이에서 data가 공유되는지 아닌지 프로그래머가 지정할 수 있음
#include <omp.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
/* sequential code */
#pragma omp parallel
{
printf("I am a parallel region.");
}
/* sequential code */
return 0;
}
#pragma omp parallel for
for (i = 0; i < N; i++) {
c[i] = a[i] + b[i];
}

4. Grannd Central Dispatch(GCD) for macOS, iOS

  • run-time library, API, language extensions의 조합
  • 개발자는 parallel할 코드부분을 지정하기만 하면된다. (OpenMP와 유사)
  • task에서 병렬수행할 코드부분을 지정하면 GCD가 알아서 관리해줌
  • Scheduling: despatch queue and thread pool
    • serial queue: FIFO order, sequentially removed, 다른 task전에 실행을 끝냄
    • concurrent queue: FIFO order, several tasks as a time, 한번에 여러개의 task가 running state로 바뀔 수 있음(병렬실행)

5. Intel Thread Building Blocks

  • intel에서 씀
  • C++기반
  • parallel task를 구분하고, TBB task scheduler가 underlying threads(process와 연결되어있는 kernel thread와 유사)로 mapping
  • task scheduler는 balancing과 cahche aware를 제공
for (int i = 0; i < n; i++) {
apply(v[i]);
}
parallel for (size t(0), n, [=](size t i) {apply(v[i]);});

4.6 Threading Issues

threads를 사용할 때 주의해야할 점들에 대해서 알아볼것이다.


fork() and exec() system calls

single thread에서 fork() system call을 호출하면 새로운 process공간을 별도로 생성하고, fork() system call을 호출한 부모 process 공간의 데이터들을 복사한다.

그렇다면 multithreaded 환경에서는 fork()수행할때 어떻게 되는가???

  • thread안에서 fork()를 call하면, all thread 복제할수도, 하나의 thread만 복제할수도 있음 각 경우를 살펴볼까???
    • exec() system call: 모든 threads를 포함해 전체 process를 교체함!
    • fork() and exec() 바로: fork()랑 exec()바로하면 fork()가 calling thread에 대해서만 복제함
    • fork() without exec(): 모든 threads 복제

Signal Handling

multithread환경에서 signal이 발생했을때, 어느 thread에게 signal을 전달해야 하는가?

  • Signal: 특정 event를 알려주는데, synchronously일 수도, asynchrononusly일 수도
  • signal pattern
    1. 특정 event가 발생했을 때 생성됨 뭐가? signal이
    2. signal이 process에 전달된다..
    3. 일단 배달된 signal에 대해서는, 반드시 처리해야한다.
  • Ex) synchronous signal인 illegal memory access랑 division by 0의 상황이 발생하면, signal은 이 문제가 발생한 process로 전달된다!
  • Ex2) asynchronous signal인, 외부로부터 발생하는 signal에 대해서, 우리가 ctrl + c하면 process 중단이 실행되는데, 이는 인위적인 갑작스러운 signal이고, 이 signal은 일반적으로 다른 process로 전달된다.
  • signal handlers: signal을 처리하는 software
    • 보통 default handler가 존재함
    • 근데 user가 직접 define할 수도 있음
  • Multithread process에서 singal 발생시 전달한 thread를 선택하는 경우들
    1. signal이 적용될 thread에게 전달 (synchronous)
    2. 모든 thread에게 전달 (ex.ctrl+c)
    3. 몇몇 thread에게 선택적으로 전달 (ex. pthread_kill(pthread_t tid, int signal) )
    4. 특정 thread가 모든 signal을 전달받도록 지정(ex. kill(pid_t pid, int signal))

Thread Cancellation

thread가 끝나기 전에 강제 종료시키는 것이다. 예를 들어 여러 thread가 parallel하게 DB를 검색하고 있다가 원하는 결과를 찾았으면 나머지 threads들은 취소해도 된다. 이렇게 취소해도 되는 thread를 target thread라고 한다.

그래서 target thread 취소에 두 가지 방식이 있는데

  1. Asynchronous cancellation: 한 thread가 즉시 target thread를 종료시킴
  2. Deterred cancellation: target thread가 주기적으로 자기가 termination 되야 하는지 점검
pthread t tid;
/* create the thread */
pthread create(&tid, 0, worker, NULL);
...
/* cancel the thread */
pthread cancel(tid);
/* wait for the thread to terminate */
pthread join(tid,NULL);

 

API를 활용하여 다음과 같이 cancellation state를 지정할 수 있음

근데 1번 방식(asynchronous cancellation)은 문제가 있음 뭐냐?

  • 먼저, target thread에 할당된 resource 때문, 갑자기 termination되면 할당된 resource가 올바르게 해제되지 않을 수도 있다!
  • 그리고 target thread가 다른 thread와 data를 공유하고 있고, 그 data를 갱신중에 terminated된다면, 이 data가 올바르게 갱신되지 않을 수도 있다!

→ 결국, system resource들이 다 사용불가 상태가 될 수도 있다는 문제가 있다.

2번 방식(deferred cancellation)은

cancellation point라는 게 있음

이게 뭐냐면 만약 대기중인 cancellation request가 있을때,

→ thread termination을 시키고,

→ cleanup handler function을 시켜서

→ resource를 release함

while (1) {
/* do some work for awhile */
...
/* check if there is a cancellation request */
pthread testcancel();
}

Thread-Local Storage (TLS)

  • thread는 원래 data를 sharig하잖아? 근데 thread’s copy를 가지고 싶을 때 이 TLS를 사용한다.
  • visible across function invocations: static to each thread
  • Pthread: pthread_key_t_type

Scheduler Activationns

(many-to-many/two-level model에서) multithread program을 설계할 때, thread library와 kernel간의 통신 문제를 고려해야함.

Lightweight process (LWP): user와 kernel사이에 존재하는 중간 자룍조

  • kernel thread에 붙어있음
  • user thread 입장에서는 virtual processor처럼 동작함
  • 어떤 application이냐에 따라 몇 개의 LWP가 필요할지 달라짐
    • ex) single processor의 CPU-bound application → LWP한 개 필요
    • ex2) single processor의 서로 다른 5개 파일 요청이 발생하면,→ 5개의 LWP 필요

Scheduler activation: user thread와 kernel thread가 통신하는 방법이다.

kernel: kernel에는 LWP를 제공

application: application에서는 user thread에 LWP를 scheduling함

Upcall: kernel에서 event가 발생했을때, application에게 알려줌, upcall이 발생하면 virtual processor에서 upcall handler를 이용하여 thread library에 의해 처리가 된다.

(반대로 application → kernel로 message보내는게 system call!)

그럼 Scheduler activation과정을 한 번 살펴보자.

  1. kernel은 application에 LWP set을 제공함
  2. LWP set을 받은 application은 user thread를 사용가능한 LWP로 scheduling함
  3. user thread가 수행 도중, kernel은 application에 upcall(특정 event에 대한 message)를 보냄
    • upcall은 thread library에 존재하는 upcall handler에 의해 처리됨
    • upcall handler는 LWP에서 실행됨
  4. application은 kenrel로부터 새로운 LWP를 할당받음
  5. LWP위에서 upcall handler 실행
  6. upcall handler는 blockig thread의 상태를 저장하고, blockig thread가 실행 중이던 LWP를 반환함
  7. upcall handler는 실행가능한 다른 kernel을 scheduling함

4.7 Operating-System Examples

각각의 OS에서 thread를 어떻게 구현하는지 살펴보자

Window Threads

  • one-to-one mapping

일반적인 thread의 구성요소는 다음과 같다.

  • thread ID : 각각의 thread를 유일하게 식별
  • register set: processor의 상태를 나타냄
  • program counter: 현재 실행되는 명령어 주소
  • user stack: usermode에서 실행할 때의 저장공간
  • kernel stack: kernelmode에서 실행할 때의 저장공간
  • private storage area used by various run-time libraries and dynamic link libraries(DLLs)

thread안에는 register set, stacks그리고 private storage area가 있다.

thread의 자료구조에는

  • ETHREAD: 실행 thread block
  • KTHREAD: kerel thread block
  • TEB: thread environment block

Linux Threads

linux는 fork() system call을 이용하여 process를 복제하거나 clone() system call을 이용하여 thread를 생성할 수 있는 기능을 제공한다.

clone()의 특징은 호출할 때 flag의 집합이 전달되고 이 flag를 통해 부모와 자식 task간에 얼마나 data 공유를 허용할지 결정한다. (sharing이 많이되면 → thread, 조금되면 → process)

근데 linux는 process랑 thread랑 구별하지 않는다. 주로 task라고 부른다.

다음 표는 일부 flag들임

'OS' 카테고리의 다른 글

[운영체제] CPU Scheduling  (1) 2024.05.01
[운영체제] Threads & Concurrency  (0) 2024.04.11
[운영체제] Processes(2)  (2) 2024.04.03
[운영체제] Processes  (0) 2024.04.01
[운영체제] OS 구조  (0) 2024.04.01