4.3 다중 스레드 모델(Multithreading Models)
사용자 스레드(User Thread)는 사용자 수준에서, 커널 스레드(Kernel Thread)는 커널 수준에서 제공된다
사용자 스레드는 유저 레벨 라이브러리에서 관리하며 OS 커널은 사용자 스레드 수를 인식하지 못한다.
커널 스레드는 커널의 지원을 받아 관리되며 스케줄 가능한 단위이다.
사용자 스레드와 커널 스레드는 궁극적으로 어떠한 연관 관계가 존재해야 한다. 아래는 관계를 확립하는 세 가지 일반적인 방법이다.
4.3.1 다대일 모델(Many to One Model)
많은 사용자 수준 스레드를 하나의 커널 스레드로 사상한다. 스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다. 하나의 스레드가 Block 되는 System Call을 요청할 경우, 전체 프로세스가 Block된다. 또한 한번의 하나으 ㅣ스레드만이 커널에 접근할 수 있기 때문에 다중 스레드가 다중 코어 시스템에서 병렬적으로 실행할 수 없다.
4.3.2 일대일 모델(One to One Model)
각 사용자 스레드를 각각 하나의 커널 스레드로 사상한다. 하나의 스레드가 Block된다 하더라도 다른 스레드가 실행될 수 있기 때문에 다대일 모델보다 더 많은 병렬성을 제공한다. 또한 Multiprocessor(Core)에서 다중 스레드가 병렬로 수행되는 것을 허용한다. 단점으로는 사용자 스레드를 생성하려면 상응하는 커널 스레드를 만들어야 하며 많은 수의 커널 스레드가 시스템 성능에 부담을 줄 수 있다는 것이다.
4.3.3 다대다 모델(Many to Mnay Model)
여러 개의 사용자 수준 스레드를 그보다 작은 수, 혹은 같은 수의 커널 스레드로 멀티플렉스 한다. 커널 스레드 수는 응용 프로그램 및 특정 기계에 따라 결정된다.
다대일 모델은 개발자가 원하는 만큼의 사용자 수준 스레드를 생성할 수 있지만, 커널은 한 번에 하나의 커널 스레드만 실행 가능하기 때문에 병렬성을 포기해야한다.
일대일 모델은 개발자가 너무 많은 스레드를 생성하지 않도록 주의해야 한다.
이러한 단점을 다대다 모델에서는 개선했다. 사용자 수준 스레드를 원하는 만큼 생성할 수 있고 상응 하는 커널 스레드가 병렬로 수행될 수 있다.
두 수준 모델(Two Level Model)
다대다 모델의 변형으로 많은 사용자 스레드를 적거나 같은 수의 커널 스레드로 멀티플렉스 하지만 한 사용자의 스레드가 하나의 커널 스레드에만 연관되는 것을 허용한다.
4.4 스레드 라이브러리(Thread Libaray)
스레드 라이브러리는 개발자에게 스레드를 생성하고 관리하기 위한 API를 제공한다.
스레드 라이브러리를 구현하는 데에는 방법이 두가지가 있다. 첫번째 방법은 커널의 지원 없이 완전히 사용자 공간에서만 라이브러리를 제공하는 것이다. 라이브러리를 모든 코드와 자료구조는 사용자 공간에 존재한다. 즉 시스템 콜이 아닌 사용자 공간의 지역함수를 호출하게 된다는 것이다.
두번째 방버으로 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현하는 것이다. 이 경우, 라이브러리를 위한 코드와 자료구조는 커널 공간에 존재한다. 즉 라이브러리 API를 호출하는 것은 시스템콜을 호출하는 것과 동등하다.
POSIX Thread, Windows, Java 세종류 라이브러리가 주로 사용된다.
자바 라이브러리는 higher-level library 이므로 실제 자바 스레드는 pthread 나 Windows thread library를 호출한다.
4.4.1 Pthread
POSIX이 스레드 생성과 동기화를 위해 제저한 표준 API이다. 하지만 이것은 스레드의 동작에 대한 명세(설명)일 뿐이지 그것 자체를 구현한 것은 아니다. 이 명세를 가지고 운영체제 설계자들은 나름대로의 스레드 라이브러리를 구현할 수있다. Linux, macOS등 많은 시스템이 Pthreads 명세를 구현하고 있다.
4.4.2 Windows Threads
Pthread 기법과 많이 유사하다.
4.4.3 Java Thread
스레드는 Java 프로그램의 프로그램 실행의 근본적인 모델이고, Java 언어와 API는 스레드 생성과 관리를 지원하는 풍부한 특성을 제공한다.
자바 스레드는 JVM이 관리한다. 하지만 일반적으로 기본 OS에서 제공하는 스레드 모델을 사용하여 구현한다.
크게 두가지 방법으로 생성된다.
1. Thread 클래스에서 파생된 새 클래스를 만들고 run() 메소드를 재정의하여 생성한다.
2. Runnable 인터페이스를 구현하는 클래스를 생성한다.
4.5 암묵적 스레딩(Implicit Threading)
멀티 스레드를 위한 프로그램을 개발하는 것은 개발자에게도 많은 어려움을 극복해야 한다. 따라서 이러한 어려움을 극복하고 병행 및 병렬 설계를 도와주는 한가지 방법은 스레딩 생성과 관리 책임을 개발자로부터 컴파일러와 run-time 라이브러리에게 넘겨주는 것이다. 이러한 것을 암묵적 스레딩이라고 칭한다.
Thread Pools, OpenMP, Grand Central Dispatch 세가지 방법이 존재한다.
4.5.1 스레드 풀(Thread Pools)
처음에 고정된 수의 스레드를 생성한다.
스레드 요청이 들어오면
스레드 풀에서 연결 가능한 스레드를 하나 선택하고 연결한다.
종료가 되면 스레드 할당을 해제하고 다시 스레드풀에 return 한다.
4.5.3 OpenMP
C, C++, FORTRAN으로 작성된 API와 컴파일러 디렉티브의 집합이다.
공유 메모리 환경(Shared Memory Env)에서 병렬 프로그램ㅇ을 할 수 있도록 도움을 준다.
병렬도 실행될 수 있는 블록을 찾아 병렬 영역으로 부른다. 개발자는 자신들의 코드중 병렬 영역에 컴파일러 디렉티브를 삽입한다. 이 디렉티브는 OpenMP 실행시간 라이브러리에 해당 영역을 병렬로 실행하라고 지시한다.
4.6 스레드와 관련된 문제들(Threading Issues)
다중 스레드 프로그램을 설계할 때 고려해야 할 몇가지 문제들이 존재한다.
4.6.1 Fork() or Exec() System Call
만일 한 프로그램의 스레드가 fork()를 호출하면 새로운 프로세스는 모든 스레드를 복제해야 하는가 아니면 한 개의 스레드만 가지는 프로세스여야 하는가?
몇몇 UNIX 기종은 이 두가지 fork()를 둘다 제공한다. 모든 스레드를 복제하거나 호출한 스레드만 복제한다.
4.62 신호처리(Siganl Handling)
신호(Signal)은 UNIX에서 프로세스에 어떤 이벤트가 일어났음을 알려주기 위해 사용된다. 신호는 근원지 및 이유에 따라 동기식 또는 비동기식으로 전달될 수 있다. 신호는 다음과 같은 형태로 전달된다.
1. 신호는 특정 이벤트가 일어나야 생성된다.
2. 생성한 신호가 프로세스에 전달된다.
3. 신호가 전달되면 반드시 처리되어야 한다.(Default or User defined Signal Handler)
커널은 모든 신호에 대해 처리를 진행하는 기본 처리기가 존재한다. 사용자 정의 신호처리기는 재정의 할수 있다.
단일 스레드의 경우는 프로세스에 바로 전달하면 된다.
다중 스레드 프로그램에서 신호처리는 복잡하다. 어떠한 스레드에 신호를 전달해야하는지 모르기 때문이다.
이것에 대한 4가지 선택이 존재한다.
1. 신호가 적용될 모든 스레드에게 전달된다.
2. 모든 스레드에게 전달된다.
3. 몇몇 스레드에만 선택적으로 전달한다.
4. 특정 스레드가 모든 신호를 전달받도록 지정한다.
4.6.3 스레드 취소(Thread Cancellation)
스레드 취소란 스레드가 종료되기 전에 강제 종료시키는 작업을 일컫는다.
이처럼 취소되어야할 스레드를 목적 스레드(Target Thread)라고 부른다. 취소는 두가지 방식으로 발생한다.
1. 비동기식 취소(Asynchronous Cancellation) : 한 스레드가 즉시 목적 스레드를 강제 종료 시킨다.
2. 지연 취소(Deffered Cancellation) : 목적 스레드가 주기적으로 자신이 강제 종료되어야 할지를 점검한다. 이 경우 목적 스레드가 질서정연하게 강제 종료될 수 있는 기회가 만들어진다.
이러한 스레드 취소는 할당된 자원 문제때문에 어렵다. 또한 공유되는 데이터를 갱신하는 와중 취소 요청이 들어와도 문제가 있다.
pthread_cancel()은 3가지 취소모드를 지원한다.
스레드 취소가 비활성화 되어있다면 스레드를 취소할 수 없다. 대신 취소 요청은 계속 보류 상태로 유지되므로 스레드는 나중에 취소를 활성화 하고 취소 요청에 응답할 수 있다.
기본 취소 유형은 지연 취소이다. 스레드가 취소점(Cancellation Point)에 도달한 경우에만 취소가 발생한다.
4.6.4 스케줄러 액티베이션(Scheduler Activations)
스레드 라이브러리와 커널의 통신 문제이다.
다대다 및 두 수준 모델에서 반드시 해결해야 할 문제이다. 이 구조를 구현하는 많은 시스템은 사용자와 커널 스레드 사이에 중간 자료구조를 설정한다. 경량 프로세스(Lightweight Process, LWP)라고 칭한다.
이는 응용 프로그램이 사용자 스레드를 수행하기 위해 스케줄 할 가상 프로세스 처럼 보인다. 각 LWP는 하나의 커널 스레드에 부속되어 있으며 물리 처리기에서 스케줄 하는 대상은 바로 이 커널 스레드이다. 커널 스레드가 I/O를 위해 Block된다면 LWP 도 Block되며 연결된 유저 스레드도 Block된다.
효율적으로 실행되기 위해 임의의 개수 LWP가 필요로 할 수도 있다. 하나의 처리기상에서 실행되는 CPU 중심 응용 프로그램(CPU Bound)을 고려해보자. 이 시나리오에서 한순간에 오직 하나의 스레드만이 실행될 수 있다. 따라서 하나의 LWP면 충분하다,. 그러나 입출력 중심 응용 프로그램(I/O Bound)은 여러개의 LWP를 필요로 할 수도있다. 만약 5개의 파일 읽기 요청이 발생했다고 가정하자, 모든 LWP가 I/O를 대기하며 커널 내부에서 대기할 수 있기 때문에 5개의 LWP가 필요하다.(4개의 LWP라면 어떤 LWP가 커널에서 복귀할 때 까지 대기해야 한다)
사용자 스레드와 커널 스레드 간의 통신방법으로 스케줄러 액티베이션이 있다. 커널은 응용 프로그램에게 LWP의 집합을 제공하고 프로그램은 사용자 스레드를 가용한 LWP로 스케줄 한다. 커널은 프로그램에게 이벤트에 대해 알려줘야 하는데 이 프로시저를 upcall이라 부른다.
Upcall을 일으키는 한 이벤트는 응용 스레드가 Block 될때 발생한다. 커널은 스레드가 Block되려고 한다는 사실과 해당 스레드의 식별자를 알려주는 upcall을 한다. 이후 커널은 새로운 LWP를 응용 프로그램에 할당한다. 프로그램은 이 새로운 LWP에서 upcall 처리기를 수행하고 이 upcall 처리기는 Block 스레드의 상태를 저장하고 이 스레드가 실행 중이던 LWP를 반환한다. 그리고 upcall 처리기로 사용된 LWP는 실행 가능한 다른 스레드를 스케줄 한다.
Block 된 스레드가 기다리던 이벤트가 발생하면 커널은 이전에 Block했던 스레드가 이제 실행할 수 있다는 사실을 알려주는 또다른 upcall을 스레드 라이브러리에 한다. 이 이벤트를 처리하는 upcall 처리기도 LWP가 필요하고 커널은 새로운 LWP를 할당하거나 사용자 스레드 하나를 선점하여 해당 LWP에서 upcall 처리기를 실행한다. Block이 풀린 스레드를 실행 가능 상태로 표시한 후에 프로그램은 가용한 LWP에서 다른 실행 가능한 스레드를 실행 한다.
'책 > 운영체제' 카테고리의 다른 글
운영체제 Ch05_'CPU Scheduling-2' (0) | 2021.01.11 |
---|---|
운영체제 Ch05_'CPU Scheduling-1' (0) | 2021.01.11 |
운영체제 Ch04_'Thread and Concurrency-1' (0) | 2021.01.10 |
운영체제 Ch03_'Process-2' (0) | 2021.01.08 |
운영체제 Ch03_'Process-1' (0) | 2021.01.07 |