[OS] Threads & Concurrency

Donghun Ha·2022년 4월 16일
0
post-thumbnail

Chapter Objectives

  • 스레드의 기본 구성요소를 식별하고 스레드와 프로세스를 대조한다.
  • 다중 스레드 프로세스를 설계할 때의 주요 이점과 중대한 과제를 설명한다.
  • 스레드 풀, 포크 조인 및 그랜드 센트럴 디스패치를 포함하여 암시적 스레딩에 대한 다양한 접근 방식을 설명한다.
  • Windows 및 Linux 운영체제가 스레드를 어떻게 나타내는 지 설명한다.
  • Pthread, Java 및 Windows 스레딩 API를 사용하여 다중 스레드 응용 프로그램을 설계한다.

4. 1 Overview

프로세스가 운영체제의 스케쥴링 단위라면, 스레드는 처리 단위이다.(= CPU 이용의 기본 단위)
스레드는 스레드 ID, PC, 레지스터 모음, 그리고 스택으로 구성된다.

스레드는 프로세스와 달리 같은 프로세스 내에서 자원을 공유한다.
(프로세스는 프로텍션 도메인(프로세스에 할당되는 메모리 영역)이 지원되어 메모리 독립성을 유지한다.)

전통적인 프로세스는 단일 스레드를 가지고 있다. 하지만 프로세스가 다수의 제어 스레드(다중 스레드)를 가진다면, 프로세스는 동시에 하나 이상의 작업을 수행할 수 있다.

Screen Shot 2022-04-14 at 9.16.00 AM.png

4. 1. 1 Motivation

현대에 작동되는 대부분의 어플리케이션은 독립적인 프로세스로 실행되며 여러 개의 실행 흐름을 가진다.

이러한 어플리케이션을 다중 스레드를 이용한다고 하며, 다중 코어 시스템에서 여러 개의 코어를 사용하여 작업을 병렬적으로 수행하여 처리능력을 향상 시킬 수 있다.

예를 들어, 비슷한 작업 여러 개를 수행해야 할 때 단일 스레드 프로세스로 동작을 한다면 모든 작업을 순차적으로 진행되며 이는 작업 효율을 저하시킨다.

과거에는 이를 해결하기 위해 프로세스를 별도로 생성하여 해당 프로세스에 필요한 작업을 할당하는 방식이 보편적으로 사용되었지만, 유사한 작업을 수행하는데 큰 오버헤드가 발생하였다.

이와 달리 응용 프로그램이 다중 스레드화 되면 프로세스를 생성하는 것보다 효율적이며 처리능력이 향상된다.

이러한 이유로 대부분의 운영체제 커널과 많은 응용 프로그램에서 다중 스레드를 활용한다.

4. 1. 2 Benefits

다중 스레드 프로그래밍의 이점은 4가지의 큰 특징으로 나눌 수 있다.

  1. Responsiveness
    1. Concurrent processing: 프로세스의 일부분이 봉쇄(block)되거나, 긴 작업을 수행하고 있더라도 프로그램 실행은 계속된다.
    2. Fast response: Concurrent processing의 특성으로 인해 사용자 응답성이 증가한다.
  2. Resource sharing: 스레드는 기본적으로 자신이 속한 프로세스의 자원과 메모리를 공유한다.
  3. Economy: 스레드는 프로세스와 달리 자원을 공유하기 때문에, 스레드를 생성하거나 스레드 간 문맥 교환 시 시간과 메모리를 적게 소비한다.(문맥 교환은 일반적으로 프로세스 사이보다 스레드 사이에서 더 빠르다.)
  4. Scalability: 다중 스레드의 마지막 이점은 멀티 프로세서 구조에서 각각의 스레드가 다른 프로세스에서 병렬적으로 수행할 수 있도록 하는 확장성을 가진다.

4. 2 Multicore Programming

컴퓨팅 칩에 여러 개의 코어를 배치하는 것을 멀티 코어라고 하며, 멀티 스레드 프로그래밍은 여러 개의 코어를 효율적으로 사용하고 병행성을 향상시키는 기법을 제공한다.

싱글 코어 시스템에서는 코어가 하나의 스레드만 처리할 수 있기 때문에, 병행성은 시간이 지남에 따라 교차로 배치(인터리브)하게 된다.

하지만 여러 개의 코어가 있는 시스템에서 병행성은 시스템이 각 코어에 스레드를 할당하기 때문에 스레드가 병렬적으로 실행된다.

  • 병행성: 둘 이상의 작업을 교차로 작업하여 병렬적으로 작업하듯이 보이는 특성
  • 병렬성: 둘 이상의 작업을 동시에 수행하는 특성

AMDAHL’S LAW

암달의 법칙은 순차와 병렬 구성요소를 바탕으로 컴퓨팅 코어가 추가될 때 얻을 수 있는 성능의 추가치를 알아내는 식이다.

아래 식에서 S는 어플리케이션에서 순차적으로 진행되는 부분의 비율, N은 코어의 개수를 의미한다.

위 법칙의 흥미로운 점은, N(코어의 개수)가 무한대에 가까워질수록 속도는 S에 수렴한다.
이 점으로 인해, 어플리케이션에 순차 실행 부분이 클수록 코어를 추가하여 얻을 수 있는 성능 향상을 저하시킨다.

Screen Shot 2022-04-14 at 12.51.37 PM.png

4. 2. 1 Programming Challenge

멀티 코어 시스템이 발전함에 따라, 프로그래머도 코어의 활용도를 높일 수 있도록 코드를 작성해야 하는데,
일반적으로 멀티 코어 시스템을 위해 프로그래밍하기 위해 고려해야 할 부분이 5가지 있다.

  1. Identifying tasks: 병행 가능 작업으로 나눌 수 있는 영역을 찾아야 한다.
  2. Balance: 모든 작업이 균등한 기여도를 가지도록 작업을 나눈다.
  3. Data spliting: 작업이 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 나누어져야 한다.
  4. Data dependency: 작업이 접근하는 데이터는 둘 이상의 작업 사이에 종속성이 없는 지 검토해야 한다.
  5. Testing and debugging: 병렬도 실행되는 동시성 프로그램은 단일 스레드 프로그램에 비해 테스트와 디버깅이 훨씬 어렵다.

4. 2. 2 Types of Parallelism

병렬 실행의 유형은 크게 두 가지가 존재한다.

  1. Data Parallelism: 동일한 데이터를 나눠서, 다수의 코어에 분배한 후 코어에서 동일한 연산을 실행
  2. Task Parallelism: 태스크(스레드)를 다수의 코어에 분배하여 각 스레드 고유의 연산을 실행

Screen Shot 2022-04-14 at 1.11.15 PM.png

4. 3 Multithreading Models

스레드는 사용자 레벨과 커널 레벨의 지원을 구분하여 지원한다.
아래에서 사용자 레벨 스레드와 커널 레벨 스레드를 알아보고 두 스레드 간의 매핑 관계를 아래 모델들로 확인할 수 있다.

ULTs & KLTs

스레드는 User Level Thread와 Kernel Level Thread로 나뉘며, 각 특성은 아래와 같다.

  1. User Level Threads

    사용자 레벨 스레드는 사용자에 의해 생성되고, 커널은 위 스레드들의 존재를 알 수 없다.
    따라서 시스템은 이들을 싱글 스레드 프로세스처럼 관리한다.

    • 장점
      • 사용자 레벨 스레드는 커널 레벨 스레드보다 상대적으로 빠르고 간단하게 만들고 관리할 수 있다.
      • 사용자 레벨 스레드는 어떤 운영체제든 실행될 수 있다.
      • 사용자 레벨 스레드가 스레드 스위칭을 할 때 커널 모드에 들어가지 않아도 된다.
    • 단점
      • 사용자 레벨 스레드에서 멀티 스레드 어플리케이션은 멀티 프로세싱 장점을 사용할 수 없다.
      • 스레드가 속한 프로세스가 블록될 경우, 스레드 또한 블록된다.
  2. Kernel Level Threads

    커널 레벨 스레드는 운영체제에 의해 작동하며, 커널을 통해 스레드를 관리한다.

    프로세스와 프로세스 스레드에 대한 문맥 정보는 모두 커널에서 관리한다.
    위 이유로 커널 레벨 스레드는 사용자 레벨 스레드보다 느리다.

    • 장점
      • 커널 레벨 스레드에서 같은 프로세스 내의 스레드를 다른 프로세서에 할당할 수 있다.
      • 커널의 루틴을 멀티 스레드로 작동하게 할 수 있다.
      • 커널 레벨 스레드가 봉쇄(block)되더라도, 동일 프로세스 내의 다른 스레드는 커널에 의해 할당될 수 있다.
    • 단점
      • 커널 모드로 모드 스위치할 때, 프로세스 내에서 다른 스레드로의 전환이 필요하다.
      • 커널 레벨 스레드는 유저 레벨 스레드에 비해 생성과 관리가 느리다.

4. 3. 1 Many-to-one

다대일 모델은 다수의 유저 스레드를 한 개의 커널 스레드에 매핑한다.

스레드 관리를 유저 스레드 라이브러리로 하기 때문에 효율적이지만, 한 스레드가 봉쇄 시스템 콜을 만드는 경우, 전체 프로세스가 봉쇄된다.

멀티 스레드가 멀티 코어 시스템에서 병렬적으로 실행될 수 없어서 멀티 코어의 이점을 살릴 수 없다.
(동시성을 잃는다.)

Screen Shot 2022-04-14 at 1.51.16 PM.png

4. 3. 2 One-to-One

일대일 모델은 각 사용자 스레드를 각각의 커널 스레드로 매핑한다.

다대일 모델에 비해 더 많은 병렬성을 제공하고 커널에서 각 유저 스레드를 별개의 프로세스로 인식하기 때문에 멀티 코어 시스템에서 병렬 수행이 가능하지만, 많은 수의 커널 스레드가 성능에 부담을 줄 수 있다.

대부분의 시스템에서 커널 스레드 수를 제한하는 것의 중요성이 줄어들어서 대부분의 운영체제는 일대일 모델을 사용한다.

Screen Shot 2022-04-14 at 1.51.33 PM.png

4. 3. 3 Many-to-Many

다대다 모델은 여러 개의 사용자 스레드를 같은 수 또는 그 보다 작은 수의 커널 스레드로 매핑한다.

커널 스레드의 수는 어플리케이션이나 하드웨어에 따라 결정된다.

다대다 모델은 다대일, 일대일 모델의 단점을 어느 정도 해결하여, 개발자는 필요한 만큼 많은 사용자 스레드를 생성할 수 있다.

그리고 상응하는 커널 스레드가 멀티 프로세서에서 병렬로 수행될 수 있다.

Screen Shot 2022-04-14 at 1.51.52 PM.png

다대다 모델은 많은 논의가 있었지만, 그 중 두 수준 모델이라고 불리는 모델이 가장 융통성 있는 것으로 보이지만, 실제로 구현하기가 어렵다.

Screen Shot 2022-04-14 at 1.52.09 PM.png

4. 4 Threads Library

스레드 라이브러리는 개발자에게 스레드를 생성하고 관리하기 위한 API를 제공한다.

스레드 라이브러리를 구현하는 데에는 두 가지 방법이 있는데,

  1. 사용자 공간에서만 라이브러리를 제공(유저 모드의 지역 함수를 호출)
  2. 운영체제에 의해 지원되는 커널 수준 라이브러리 구현(결과적으로 시스템 콜 호출)

주로 사용되는 세가지 라이브러리로 POSIX Pthreads, Windows, Java가 있는데,

Pthreads: 사용자 또는 커널 수준 라이브러리

Windows: Windows 시스템에서 사용 가능한 커널 수준 라이브러리

Java: 호스트 운영체제의 라이브러리를 사용하여 구현

추가로 각 라이브러리를 사용하여 다수의 스레드를 생성할 때 사용하는 두 가지 일반적인 전략이 있다.

  1. 비동기 스레딩: 부모가 자식 스레드를 생성한 후, 부모는 자신의 실행을 재개하여 부모와 자식 스레드가 서로 독립적으로 병행하게 실행된다.
  2. 동기 스레딩: 부모 스레드가 하나 이상의 자식 스레드를 생성하고 자식 스레드 모두가 종료할 때까지 기다렸다가 자신의 실행을 재개한다.

4. 4. 1 Pthreads

Pthreads는 POSIX가 스레드 생성과 동기화를 위해 제정한 표준 API이다.
이는 스레드 동작에 대한 설명서와 같이 제공될 뿐 구현이 된 것은 아니다.
따라서, 이 설명서를 가지고 운영체제 설계자들은 Pthreads를 구현한다.

스레드 생성과 동기화를 위해 사용되는 함수는

  • pthread_create - 스레드 생성
  • pthread_join - 자식 스레드가 종료될 때까지 대기(동기 스레딩)
  • pthread_attr_init - 스레드 속성 초기화

등이 있다.

4. 4. 2 Windows Threads

Windows 스레드 라이브러리에서 스레드를 생성하는 기술은 Pthreads의 기법과 유사하다.

스레드 생성과 동기화를 위해 사용되는 함수는

  • CreateThread - 스레드 생성
  • WaitForSingleObject - 자식 스레드가 종료될 때까지 대기(동기 스레딩)

등이 있다.

4. 4. 3 Java Threads

Java 언어와 API는 스레드 생성과 관리를 풍부하게 지원하는데,
모든 Java 프로그램은 적어도 하나 이상의 단일 제어 스레드를 포함한다.

Java에서 스레드 생성과 동기화를 위해서는 Runnable을 구현하는 클래스의 인스턴스를 전달하고
아래 함수를 사용할 수 있다.(공룡책 예시 함수로 추가적인 함수들도 별도로 존재한다.)

  • start - 메모리 할당 후, JVM에 새로운 스레드 초기화
  • join - 각 스레드가 완료되기를 기다렸다가 진행(동기 스레딩)
// 생성 예시
class Task implements Runnable
{
	public void run() {
		System.out.println("I am a thread.")
	}
}

Thread worker = new Thread(new Task());
worker.start();

4. 5 Implicit Threading

멀티 코어 프로세싱의 지속적인 성장으로 수백, 수천 개의 스레드를 가진 어플리케이션이 등장했는데, 이러한 어플리케이션을 설계하는 것은 쉽지 않고 (4. 2)절에서 이야기 한 고려할 부분을 포함하여 더 많은 부분들을 고려하여야 한다.

이러한 부분들을 극복할 수 있는 방법 중 하나로 스레딩의 생성과 관리의 책임을 개발자가 아닌 컴파일러와 런타임 라이브러리에 넘기는 것이고, 이를 암묵적 스레딩이라 부른다.

위 방법을 사용하기 위해서는, 개발자가 병렬로 실행할 스레드가 아닌 작업을 식별해야 한다.

4. 5. 1 Thread Pools

특정 작업을 스레드를 만들어서 처리하는 것은, 새로운 프로세스를 만드는 것보다 효율적인 방법이지만,
모든 요청마다 새 스레드를 만들어서 제공한다면 생성과 스케쥴링으로 인해 어플리케이션의 성능 저하를 야기한다.

이런 문제를 해결하기 위한 방법 중 하나인 스레드 풀은 프로세스를 시작할 때, 일정한 수의 스레드들을 미리 풀로 만들어둔다.

정해진 수의 스레드에서 작업 큐에 들어오는 작업을 하나씩 스레드가 맡아서 처리하고, 작업이 끝난 스레드는 다시 대기 큐에서 작업을 가져와 처리한다.

이로 인해, 많은 작업 요청이 동시에 발생하더라도 대기 큐에 있다가 여유있는 스레드가 작업을 처리하기 때문에 스레드의 전체 개수는 유지되며 어플리케이션의 성능도 저하되지 않는다.

  • 장점
    • 새 스레드를 만드는 것보다 기존 스레드로 작업을 처리하는 것이 보통 더 빠르다.
    • 많은 스레드를 병렬 처리할 수 없는 시스템에서 스레드를 제한하면 안정적으로 작업을 처리할 수 있다.
    • 스레드를 만드는 구간을 작업과 분리하면 작업 실행을 커스텀 할 수 있다.

4. 5. 2 Fork Join

fork-join 모델은 프로그램이 지정된 지점에서 작업을 병렬로 분기하여 처리하고, 그 후 다시 병합하여 순차 실행은 재개하도록 하는 방법이다.

위(4. 4절)에서 join 또는 pthread_join 함수를 사용하여, 동기 스레딩하는 방식도 fork-join 모델로 알려져있다.

일반적으로 명시적 스레드 생성이라고 보지만, 암시적 스레딩에도 사용할 수 있다.
암시적 스레딩에서 사용될 때는, fork 단계에서 잠재적 병렬 처리 작업이 식별된다.

4. 5. 2. 1 Fork Join in Java

Java는 재귀 분할-정복 알고리즘에 사용되도록 fork join 라이브러리를 도입하였다.

이 라이브러리를 사용하여 분할-정복 알고리즘을 구현하면 별도의 작업 없이 fork되고 문제의 일부가 할당된다.

위 라이브러리의 일반적인 동작은 아래와 같이 실행된다.

Screen Shot 2022-04-14 at 8.22.04 PM.png

fork-join 모델은 특정 작업 단위에 도달할 때까지 재귀적으로 분기될 수 있다.

이 경우, 작업이 충분히 작은 크기여서 더 이상 추가 작업을 생성하지 않은 때를 신중하게 결정해야 한다.

Screen Shot 2022-04-14 at 8.26.15 PM.png

Java의 fork-join 모델에서 흥미로운 점은 라이브러리에서 제공하는 작업 관리가 작업자 스레드 풀을 구성하고 사용 가능한 작업자 간의 부하의 균형을 조정한다.

또한, ForkJoinPool의 각 스레드는 fork된 작업의 큐를 유지, 관리하며 스레드 큐가 비어있으면 work stealing 알고리즘을 사용하여 다른 스레드 큐에서 작업을 가져올 수 있어서 모든 스레드 간의 부하를 분산시킬 수 있다.

4. 5. 3 OpenMP

OpenMP는 C, C++, 또는 FORTRAN으로 작성된 컴파일러 지시문의 집합이자 API이며 공유 메모리 환경에서 병렬 프로그래밍을 지원한다.

OpenMP는 병렬로 실행될 수 있는 블록을 찾아 병렬 영역이라고 부르며, 개발자가 병렬 영역에 컴파일러 지시문을 삽입한다.
지시문은 OpenMP 런타임 라이브러리에 해당 영역을 병렬로 실행하라고 지시한다.

#pragma omp parallel
{
	printf("I am a parallel region.");
}

OpenMP는 병렬화를 위한 디렉티브를 다양한 방식으로 제공하며, 개발자가 병렬화 수준을 선택할 수 있게 한다.

Linux, Windows 및 macOS 시스템을 위한 다수의 오픈 소스와 컴파일러에서 사용할 수 있다.

4. 5. 4 GCD(Grand Central Dispatch)

GCD는 macOS 및 iOS 운영체제를 위해 Apple에서 개발한 기술이다.

개발자가 병렬로 실행될 코드 섹션을 식별할 수 있도록 하는 런타임 라이브러리, API 및 언어 확장의 조합이다.

GCD는 런타임 수행을 위해 태스크를 디스패치 큐에 넣어 스케쥴하고 태스크를 제거할 때, 스레드 풀에서 가용 스레드를 선택하여 태스크를 할당한다.

GCD는 두 가지 유형(직렬과 병행)의 디스패치 큐를 유지한다.

  • 직렬 큐
    • FIFO 순서대로 제거된다.
    • 태스크가 큐에서 제거되면 다른 태스크가 제거되기 전에 실행을 반드시 완료해야 한다.
    • 각 프로세스에는 고유한 직렬 큐(직렬 큐)가 있으며 개발자는 추가로 직렬 큐를 만들 수 있다.
  • 병행 큐
    • FIFO 순서대로 제거된다.
    • 한번에 여러 태스크가 제거되어 병렬로 실행할 수 있다.
    • 다수의 시스템 전역 병행 큐들이 존재하며 이는 4가지 주요 서비스 품질 클래스로 나뉜다.
      • QOS_CLASS_USER_INTERACTIVE - 사용자 대화형 클래스
      • QOS_CLASS_USER_INITIATED - 사용자 시작 클래스
      • QOS_CLASS_USER_UTILITY - 유틸리티 클래스
      • QOS_CLASS_BACKGROUND - 백그라운드 클래스

4. 5. 5 Intel Thread Building Blocks

Intel TBB는 C++에서 병렬 어플리케이션 설계를 지원하는 템플릿 라이브러리이다.

라이브러리기 때문에 특별한 컴파일러나 언어 지원이 필요하지 않다.

개발자가 병렬로 실행할 수 있는 태스크를 지정해주면, TBB 스케쥴러는 해당 태스크를 하부 스레드에 매핑한다.

태스크 스케쥴러는 부하 균형 기능을 제공하고 캐시를 인지한다.
(캐시를 인지한다 = 빠르게 실행되는 태스크에 우선순위를 부여한다)

4. 6 Threading Issues

아래에서는 다중 스레드 프로그램을 설계할 때, 고려해야할 문제들에 대해 볼 수 있다.

4. 6. 1 The fork() and exec() System Calls

멀티 스레드 프로그램에서 fork()를 하는 경우, 의미가 달라질 수 있다.

모든 스레드를 복사할 지, fork()를 호출한 스레드를 복사할 지 선택해야 하는데, 몇몇 UNIX 시스템에서는 이 둘 모두 지원한다.

fork()만 하여 프로세스의 복제본을 가지고 싶은 경우, 모든 스레드를 복제해서 만들어야 하지만
fork() 후 exec()을 호출하여 기존 프로세스를 대체할 경우, 모든 스레드를 복제하는 일은 불필요하다.

fork 시스템 콜은 스레드라는 개념이 없던 시기에 만들어졌기 때문에 thread-safe를 보장하지 않는다.
따라서, 위 두 가지 방식을 지원하지 않는 멀티 스레드 환경에서는 이를 고려하여 사용해야 한다.

4. 6. 2 Signal Handling

시그널은 UNIX에서 프로세스에 어떤 이벤트가 발생했음을 알려주기 위해 사용한다.

신호는 동기식 또는 비동기식으로 전달될 수 있는데, 모든 신호는 다음과 같은 형태를 가진다.

  1. 신호는 특정 이벤트가 일어나야 생성된다.
  2. 생성된 신호가 프로세스에 전달된다.
  3. 신호가 전달되면 반드시 처리되어야 한다.
  • 동기식 신호 동기식 신호는 불법적인 메모리 접근, 0 나누기 등 실행중인 프로그램이 이러한 행동을 하면 발생한다.
    동기식 신호는 신호를 발생시킨 연산을 수행한 동일한 프로세스에 전달된다.
  • 비동기식 신호 비동기식 신호는 프로세스 외부에서 발생하고 그 신호를 비동기식으로 전달 받는다.

모든 신호는 기본적으로 디폴트 시그널 핸들러에 의해 처리되며, 이는 사용자 정의 시그널 핸들러로 대체할 수 있다.

단일 스레드 프로그램에서의 신호 처리는 간단하다. 하지만 프로세스가 여러 스레드를 가지고 있는 경우 별도로 처리해야 하는데, 대부분의 UNIX는 스레드에 받아들일 신호와 봉쇄할 신호를 지정할 수 있는 선택권을 준다.

4. 6. 3 Thread Cancellation

스레드 취소는 스레드가 끝나기 전에 강제 종료 시키는 작업을 뜻한다.

취소되어야 할 스레드를 목적 스레드라고 부르며, 취소는 두 가지 방식으로 발생할 수 있다.

  1. 비동기식 취소: 한 스레드가 즉시 목적 스레드를 강제 종료
    비동기식으로 스레드를 취소하면, 일부 자원을 회수하지 못하는 경우도 발생한다.
  2. 지연 취소: 목적 스레드가 주기적으로 자신이 강제 종료되어야 할 지 점검
    지연 취소에서 스레드는 자신이 취소되어도 안전하다고 판단되는 시점에 취소 여부를 검사할 수 있다.

Pthreads는 3가지 취소 모드를 지원하는데 아래와 같다.

  • Off: 취소 비활성화
  • deferred(지연): 사용 가능
  • asynchronous(비동기식): 사용 가능

위에서 설명된 비동기식 취소의 문제 때문에 Pthreads 문서에서는 비동기 취소를 권장하지 않는다.

Java의 스레드 취소는 Pthread의 지연 취소와 유사한 정책을 사용한다.

4. 6. 4 Thread-Local Storage

각각의 스레드는 고유한 스택을 갖기 때문에, 스택에 저장된 데이터는 스레드 별로 고유하다. 하지만 정적 변수와 전역 변수의 경우에는 프로세스 내의 모든 스레드에서 공유된다.

하지만 상황에 따라서 각 스레드가 자신만 액세스할 수 있는 데이터(정적, 전역 변수)를 가져야 할 때도 있다.
그 때, 스레드-로컬 저장장치(TLS)를 사용한다.

TLS는 API 또는 컴파일러가 지원할 때, 사용 가능하다.

스레드 로컬 스토리지 작동 방식

4. 6. 5 Scheduler Activations

(4. 3. 3)절에서 살펴본 다대다 모델에서는 스레드 라이브러리와 커널의 통신 문제를 반드시 해결해야 한다.

사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중 하나는 스케줄러 액티베이션이라는 방법이다.

커널은 어플리케이션에 사용자 스레드와 커널 스레드 사이의 자료구조인 LWP의 집합을 제공하고 어플리케이션은 사용자 스레드를 사용 가능한 가상 처리기로 스케줄한다. 또한, 커널은 어플리케이션에게 이벤트에 대해 알려야 한다.

이 절차를 upcall이라 부르고, upcall은 스레드 라이브러리의 upcall 처리기에 의해 처리된다.

LWP는 Low-weight Process의 약자로 경량 프로세스라 부르는데,
이는 하나 이상의 사용자 스레드를 지원하고, 커널 스레드 하나에 매핑되어 커널에 의해 독립적으로 스케줄링된다.

스레드 라이브러리에 의해 관리되는 여러 사용자 수준 스레드는 하나 이상의 LWP 위에 배치될 수 있다.
하지만 일부 운영 체제에서는 커널 스레드를 나타내기도 하고, 사용자 스레드(Linux)를 나타내기도 한다.

경량 프로세스 매핑

4. 7 Operating-System Examples

Windows와 Linux 시스템에서 스레드를 어떻게 구현하는 지 탐구한다.

4. 7. 1 Windows Threads

윈도우는 어플리케이션을 프로세스 형태로 실행하고, 프로세스는 하나 이상의 스레드를 가진다.

윈도우 스레드의 구성 요소

  • 스레드 ID
  • 레지스터 집합
  • 프로그램 카운터(PC)
  • 사용자 스택, 커널 스택
  • 개별 데이터 저장 영역

레지스터 힙합, 스택, 개별 데이터 저장 영역은 그 스레드의 문맥으로 불린다.

윈도우 스레드의 자료 구조

  • ETHREAD - 실행 스레드 블록 속한 프로세스를 가리키는 포인터, 스레드가 실행할 루틴 주소, KTHREAD에 대한 포인터 등을 가진다.
  • KTHREAD - 커널 스레드 블록 스레드의 스케줄링 및 동기화 정보, 커널 스택과 TEB에 대한 포인터를 가진다.
  • TEB - 스레드 환경 블록 사용자 모드에서 실행될 때 접근하는 사용자 공간 자료구조이다.
    스레드 ID, 사용자 스택, 스레드-로컬 저장장치 등을 저장하기 위한 배열등을 가진다. Screen Shot 2022-04-15 at 11.13.58 AM.png

4. 7. 2 Linux Threads

Linux는 clone()이라는 시스템 콜을 통해 스레드를 생성할 수 있는 기능을 제공하지만,
프로세스와 스레드를 구별하지 않고, 태스크라는 용어를 사용한다.

Linux 커널이 태스크를 표현하는 방식 때문에 다양한 공유 수준을 선택할 수 있는데,
이를 설정하지 않고 clone() 시스템 콜을 이용하면 fork()가 호출된 것과 유사한 기능을 제공한다.

Practice Exercises

4 .1 다중 스레딩이 단일 스레드 솔루션보다 더 나은 성능을 제공하는 세 가지 프로그래밍 예제를 제시하라.

4 .2 Amdahl의 법칙을 사용하여 (a) 2개의 처리 코어와 (b) 4개의 처리 코어에 대해 60%의 병렬 구성요소를 가진 응용 프로그램의 속도 향상 이득을 계산하라.

4 .3 4.1절에 설명된 다중 스레드 웹 서버는 작업 병렬 처리인가 혹은 데이터 병렬 처리인가?

4 .4 사용자 수준 스레드와 커널 수준 스레드의 2가지 차이점은 무엇인가? 한 유형의 스레드가 다른 유형의 스레드보다 유리한 상황은 언제인가?

4 .5 커널 수준 스레드 사이에서 문맥 교환을 위해 커널이 해야할 일을 설명하라.

4 .6 스레드가 생성될 때 어떤 자원이 사용되는가? 프로세스가 생성될 때 사용되는 자원과의 차이점은 무엇인가?

4 .7 다대다 모델을 사용하여 운영체제가 사용자 수준 스레드를 커널에 매핑하고 매핑은 LWP를 사용한다고 가정하자. 또한 개발자는 실시간 시스템에서 사용할 실시간 스레드를 만들 수 있다고 하자. 실시간 스레드를 LWP에 바인딩해야 하는가? 여러분의 답을 설명하라.

profile
Corca Backend Engineer, dha

0개의 댓글