Computer Science

운영체제[OS] 4. Threads and Concurrency

parkchwl 2024. 4. 17. 14:02

 

이 글은 고려대학교 김민호 교수님의 운영체제강의를 토대로 작성한 글입니다.
This post is based on the operating system lecture by Prof. Kim Min-ho of Korea University.


# Chapter 4 : Threads and concurrency

 대부분의 현대 어플리케이션은 멀티쓰레드로 이루어져 있습니다.

 쓰레드는 어플리케이션 내에서 실행됩니다.

 어플리케이션의 여러 작업을 별도의 쓰레드로 구현할 수 있습니다.

 예를들어,

 디스플레이 업데이트

 데이터 가져오기

 맞춤법 검사

 네트워크 요청 처리

 

 프로세스 생성 무거운 작업이지만, 쓰레드 생성은 가벼운 작업입니다.

❖ 코드를 단순화하고 효율성을 높일 수 있습니다.

❖일반적으로 커널도 멀티쓰레드입니다.

 

싱글 스레드 프로세스 / 멀티 쓰레드 프로세스

 

멀티스레드 서버 구조

단일 쓰레드인 경우, 프로세스는 새 요청에 응답하기 위해 일시적으로 일시 정지해야 합니다.

쓰레드의 장점

 응답성 - 프로세스의 일부가 차단되어 있어도 계속 실행할 수 있게 함으로써 특히 사용자 인터페이스에 중요합니다.

 자원 공유 - 쓰레드는 프로세스의 리소스를 공유하며, 이는 공유 메모리나 메시지 전달보다 쉽습니다.

 경제성 - 프로세스 생성보다 저렴하며, 쓰레드 전환은 컨텍스트 전환보다 오버헤드가 적습니다.

 확장성 - 멀티코어 아키텍처를 활용할 수 있습니다.

쓰레드의 장점 - 예시

❖ 웹 서버,

• 단일 쓰레드 웹 서버: 클라이언트는 요청이 처리될 때 까지 기다려야 할 수 있습니다.

• 다중 프로세스 웹 서버: 쓰레드가 보편화되기 전에 사용되었으며, 새로운 프로세스를 생성하는 데 많은 오버헤드가 있었습니다,

• 다중 쓰레드 웹 서버: 쓰레드 생성에 대한 오버헤드가 적고, 여러 클라이언트에 대한 동시 서비스가 가능합니다.

 

 현재 많은 운영 체제 커널은 멀티쓰레드로 구성되어 있습니다.

  여러 쓰레드가 커널에서 작동합니다.

  각 쓰레드는 장치 관리 인터럽트 처리와 같은 특정 작업을 수행합니다.

 

 

멀티코어 프로그래밍

 멀티코어 및 멀티프로세서 시스템의 등장은 처리 능력을 크게 향상시켰지만, 동시에 프로그래머에게 새로운 도전 과제를 제시했습니다.

 병렬은 시스템이 동시에 여러 작업을 수행할 수 있음을 의미합니다.

 병행 더 많은 작업이 진행되는 것을 지원합니다.

• 단일 프로세서/코어의 경우, 스케줄러가 동시성을 제공합니다.

 

병행 vs 병렬

병행 처리(Concurrency)

병행 처리는 시스템이 여러 작업이 동시에 실행되는 것처럼 보이도록 관리하는 기능을 의미합니다. 단일 CPU코어에서 실제로 한 번에 하나의 작업만 실행될 수 있더라도 여러 작업이 동시에 진행되는 착각을 불러일으킵니다.

 단일 코어 실행: 단일 코어 시스템에서는 운영 체제가 스케줄러를 사용하여 프로세스 간에 빠르게 전환합니다. 각 프로세스는 지침을 실행하기 위해 짧은 시간 슬라이스를 할당받습니다. 프로세스의 시간 슬라이스가 만료되면 CPU는 큐의 다음 프로세스로 전환됩니다. 이러한 빠른 컨텍스트 전환은 병행 처리의 인상을 줍니다.

 

병렬 처리(Parallelism)

병렬 처리는 시스템이 여러 작업을 실제로 동시에 실행하는 기능을 의미합니다. 이는 멀티코어 또는 멀티프로세서 시스템에서 달성되며, 각 코어는 서로 다른 작업에 대한 지침을 독립적으로 실행할 수 있습니다.

 멀티코어 실행: 멀티코어 시스템에서는 각 코어가 별도의 프로세서 역할을 합니다. 여러 프로세스나 스레드를 서로 다른 코어에 할당할 수 있어 단일 코어 시스템의 병행 처리에 비해 훨씬 더 빠른 전체 처리 속도를 제공합니다.

 

결론적으로, 병행 처리 단일 코어 시스템에서 멀티테스킹의 착각을 제공하는 반면, 병렬 처리 멀티 코어 프로세서에서는 진정한 동시실행을 가능하게 합니다.

 

 병렬성의 유형

  • 데이터 병렬성: 동일한 데이터 하위 집합을 여러 코어에 분배하여 각각 동일한 작업을 수행합니다.
  • 작업 병렬성: 스레드를 코어에 분산하여 각 스레드가 고유한 작업을 수행합니다.

데이터 병렬 처리: 데이터로 작업 분할

  • 예시: 크기가 N인 배열을 더하는 것을 고려해 보세요. 데이터 병렬 처리에서:
    • 단일 코어: 단일 코어에서는 전체 추가 작업이 순차적으로 수행되어 인덱스[0]에서 [N-1]까지 요소를 추가합니다.
    • 듀얼 코어: 두 개의 코어가 있는 경우 배열을 절반으로 나눌 수 있습니다. 코어0(쓰레드 A)은 요소[0]에서 [N/2-1]까지 처리하고 코어 1(쓰레드 B)은 요소[N/2]에서 [N-1]까지 처리합니다. 두 코어 모두 할당된 요소를 동시에 추가합니다.

작업 병렬 처리: 작업으로 작업 분할

  • 예시: 다시 크기가 N인 배열 시나리오를 고려해 보세요. 듀얼 코어 시스템에서 작업 병렬 처리를 사용하면:
    • 코어 0은 배열 요소를 추가하는 작업을 수행할 수 있습니다.
    • 코어 1은 동시에 동일한 배열에 대해 평균 계산과 같은 완전히 다른 통계 작업을 수행할 수 있습니다.

 

암달의 법칙 (Amdahl's Law)

암달의 법칙은 멀티 코어 시스템에서 병렬 처리의 한계를 나타내는 중요한 개념입니다. 이 법칙은 응용 프로그램의 성능 향상이 병렬화할 수 없는 직렬 부분에 의해 제한된다는 것을 보여줍니다.

핵심 용어 정의

  • S: 응용 프로그램의 직렬 부분 비율 (0 ~ 1 사이의 값)
  • N: 프로세싱 코어의 개수
  • 속업 (Speedup): 병렬 처리를 사용할 때 단일 코어를 사용하는 것보다 프로그램 실행 속도가 얼마나 빨라지는지 나타내는 값

법칙 설명

암달의 법칙에 따르면, N개의 코어를 사용하여 프로그램을 실행할 때 얻을 수 있는 최대 속업은 다음과 같이 계산됩니다.

속업 = 1 / (S + (1 - S) / N)

 

응용 프로그램이 75% 병렬 처리가 가능하고 25% 직렬 처리가 필요하다고 가정했을 때 (S = 0.25). 1개 코어에서 2개 코어로 이동할 때 얻을 수 있는 이론적 speed up을 계산한다.

속업 = 1 / (0.25 + (1 - 0.25) / 2)

속업 = 1.6

따라서  예시에서 2 코어를 사용하면 1 코어보다 1.6 빠르게 프로그램을 실행할  있습니다.

멀티쓰레딩 모델

 사용자 쓰레드 - 사용자 수준 쓰레드 라이브러리에서 관리됩니다.

 사용자 쓰레드는 커널위에 지원되며, 커널 지원없이 관리됩니다.

 세 가지 주요 쓰레드 라이브러리:

• POSIX Pthreads

• Windows threads

• Java threads

 

 커널 쓰레드 - 커널에서 직접 지원 및 관리 됩니다.

예시 - 현대적인 대부분의 운영 체제는 커널 쓰레드를 지원합니다.

 Windows

 Linux

 Mac OS X

 iOS

 Android

사용자 쓰레드와 커널 쓰레드 간의 관계 3가지

 

多 : 1

 여러 사용자 수준 쓰레드가 단일 커널 쓰레드에 매핑됩니다.

- 장점

  • 원하는 만큼 많은 사용자 쓰레드를 생성할 수 있습니다.

- 단점

  • 하나의 쓰레드가 블록되면 모든 쓰레드가 블록됩니다.
  • 멀티코어 시스템에서 여러 쓰레드가 동시에 실행되지 않을 수 있습니다. 왜냐하면 한 번에 하나의 쓰레드만 커널에서 실행되기 때문입니다.

현재 이 모델을 사용하는 시스템은 매우 적습니다.

❖예시: 

  • Solaris Green Threads
  • GNU Portable Threads

1 : 1

❖ 각 사용자 수준 쓰레드가 커널 쓰레드에 매핑됩니다.

❖ 사용자 수준 쓰레드를 생성하면 커널 쓰레드가 생성됩니다.

 多 : 1 보다 더 많은 동시성을 가집니다.

❖ 과도한 오버헤드로 인해 프로세스 당 쓰레드 수가 제한될 수 있습니다. 쓰레드를 너무 많이 만들지 않도록 주의해야 합니다.

❖ 예시:

 Windows

 Linux

 

多 : 多

 많은 사용자 수준 쓰레드가 많은 커널 쓰레드에 매핑됩니다.

 운영 체제가 충분한 수의 커널 쓰레드를 생성할 수 있게 합니다.

 그렇지 않으면 그다지 흔하지 않습니다.

    대부분의 시스템에서 처리 코어 수가 증가함에 따라 커널 쓰레드의 수를 제한하는 것이 덜 중요해졌습니다.

  • M:M 유사하지만 사용자 쓰레드를 커널 쓰레드에 바인딩할  있습니다.

 

쓰레드 라이브러리

 쓰레드 라이브러리는 프로그래머에게 쓰레드를 생성하고 관리하기 위한 API를 제공합니다.

 구현하는 주요 방법은 두 가지입니다.

  • 커널 지원 없이 완전히 사용자 공간에서 라이브러리를 제공하는 것입니다.
    • 모든 코드 및 데이터 구조는 사용자 공간에 존재합니다.
    • 모든 함수 호출은 사용자 모드에서 실행되며 커널 모드가 아닙니다.
  • 운영 체제에서 지원하는 커널 수준 라이브러리를 구현하는 것입니다.
    • 모든 코드 및 데이터 구조는 커널 공간에 존재합니다.
    • 함수를 호출하면 커널에 시스템 호출이 발생합니다.

 주요 쓰레드 라이브러리는 다음과 같습니다.

  • POSIX Pthreads: Solaris, Linux, Mac OS X, Tru64 UNIX에서 사용
  • Win32/64 Thread: Windows에서 사용
  • Java Thread: Java에서 사용

 스레드 라이브러리 - Pthreads

Pthreads(POSIX Threads)는 POSIX 표준 (IEEE 1003.1c)에서 정의된 스레드 생성 및 동기화를 위한 API(Application Programming Interface)입니다. Pthreads는 사용자 수준 또는 커널 수준 중 하나로 제공될 수 있습니다.

주요 특징:

  • 표준화: Pthreads는 POSIX 표준의 일부이므로 다양한 유닉스 운영 체제에서 일관된 API를 제공합니다. 이는 프로그램 이식성을 높여줍니다.
  • 스레드 생성 및 관리: Pthreads는 스레드 생성, 종료, 조인(join) 등을 위한 함수를 제공합니다.
  • 동기화 메커니즘: Pthreads는 뮤텍스(mutex), 조건 변수(condition variable), 세마포어(semaphore)와 같은 동기화 메커니즘을 제공하여 여러 스레드가 공유 자원에 동시에 액세스하는 경우 데이터 손상 및 경쟁 조건을 방지합니다.
  • 명세만 제공: Pthreads는 API의 기능과 동작 방식을 명세하지만 실제 구현은 라이브러리 개발자에게 맡겨져 있습니다.

이용 환경:

Pthreads 일반적으로 리눅스, macOS X 같은 유닉스 계열 운영 체제에서 널리 사용됩니다. 이는 이러한 운영 체제에서 다중 스레드 프로그래밍을 지원하는 기본 API입니다.

쓰레드 라이브러리 - Windows 쓰레드 라이브러리

  • 커널 수준 라이브러리로 제공됩니다.
  • 1:1 매핑을 구현합니다.

쓰레드 라이브러리 - 자바 쓰레드

 자바 쓰레드는 JVM에 의해 관리됩니다.

 자바 쓰레드는 다음과 같이 생성될 수 있습니다:

  • Thread 클래스를 확장하여
  • Runnable 인터페이스를 구현하여

 JVM이 호스트 OS 상에서 실행되므로, 자바 쓰레드 API는 일반적으로 호스트 시스템에서 사용 가능한 쓰레드 라이브러리를 사용하여 구현됩니다.

  • Windows 시스템에서: Win32/64 쓰레드 라이브러리를 사
  • Linux  Unix 시스템에서: pthread 라이브러리를 사용

암묵적 스레딩

  • Why? 스레드의 수가 증가함에 따라 명시적 스레드로 프로그램을 작성하는 것이 더 어려워지므로 인기를 얻고 있습니다.
  • 스레드의 생성과 관리는 프로그래머가 아닌 컴파일러와 런타임 라이브러리에 의해 수행됩니다.

다섯 가지 방법을 탐구합니다:

  • Thread Pools
  • Fork-Join
  • OpenMP
  • Grand Central Dispatch
  • Intel Threading Building Blocks

 

쓰레드 풀

 스레드 플은 작업을 대기하는 스레드들의 집합체입니다.

 프로세스는 시작 시 일정 개수의 스레드를 생성하여 풀에 저장합니다

 서버는 요청을 받으면 풀에서 사용가능한 스레드를 깨워 요청을 처리하도록 지시합니다.

 스레드가 서비스를 완료하면, 풀로 돌아가 다음 작업을 기다립니다.

 풀에 사용 가능한 스레드가 없으면 서버는 하나가 여유롭게 될 때까지 기다립니다.

 

 장점:

 기존 스레드를 사용하여 요청을 처리하는 것이 일반적으로 새로운 스레드를 만드는 것보다 빠릅니다.

 응용 프로그램에서 사용하는 스레드의 최대 수를 풀의 크기로 제한할 수 있습니다. 이는 특히 작은 시스템 리소스를 가진 시스템에서 유용합니다.

 

 Windows API는 스레드 풀을 지원하는 운영 체제 중 하나입니다.

 

 Fork-Join 병렬 프로그래밍은 병렬 컴퓨팅에서 사용되는 패러다임입니다. 이 패러다임은 다음과 같은 특징을 가집니다.

  • 분할(Fork)과 병합(Join):
    • 큰 작업을 서브 작업(sub task)으로 분할(Fork)합니다.
    • 각 서브 작업은 별도의 스레드에서 병렬적으로 실행됩니다.
    • 서브 작업의 결과를 다시 합병(Join)하여 최종 결과를 얻습니다.
  • 분할 정복 문제 해결에 적합합니다:
    • 이 패러다임은 큰 문제를 작은 문제들로 분할하여 해결하는 분할 정복(divide-and-conquer) 문제 해결 전략에 적합합니다.
    • 각 작은 문제들은 병렬적으로 실행되어 전체적인 작업 속도를 향상시킵니다.

 fork() 및 exec() 시스템 호출의 의미론적 차이

  • 시그널 처리
    • 동기식 및 비동기식
  • 대상 스레드의 스레드 취소
    • 비동기식 또는 지연
  • 스레드 로컬 저장소
  • 스케줄러 활성화

  fork()과 exec() 시스템 호출의 의미

  fork() 시스템 호출:

  • 새로운 프로세스를 생성하는 시스템 호출입니다.
  • 생성된 프로세스를 자식 프로세스 (child process) 라고 부르며, 호출한 프로세스를 부모 프로세스 (parent process) 라고 부릅니다.
  • 자식 프로세스는 부모 프로세스의 복사본 (copy) 이라고 할 수 있습니다.

  fork()와 스레드:

  • fork() 시스템 호출은 프로세스 를 복제하는 것이므로, 스레드 자체를 복제하지 않습니다.
  • 부모 프로세스의 메모리 공간, 파일 디스크립터 등의 리소스 는 자식 프로세스와 공유 됩니다.
  • 따라서 프로그램에서 fork()  호출하면 생성된 자식 프로세스는 단일 스레드 (single-threaded)  실행됩니다.

시그널 처리

  시그널은 유닉스 시스템에서 프로세스에게 특정 이벤트가 발생했다는 것을 알리는 메커니즘입니다.

  시그널 처리 과정은 다음과 같이 진행됩니다.

1) 시그널 발생 (Signal is generated by particular event):

2) 시그널 전달 (Signal is delivered to a process):

3) 시그널 처리 (Signal is handled by one of two signal handlers):

• 기본 핸들러(default)

• 사용자 정의 핸들러 (user-defined signal handler)

 

모든 시그널에는 커널이 시그널을 처리할 때 실행하는 기본 핸들러가 있습니다.

 사용자 정의 시그널 핸들러는 기본 핸들러를 덮어쓸 수 있습니다.

  단일 스레드인 경우 시그널은 항상 프로세스에 전달됩니다.

 멀티스레드 환경에서 시그널은 어디에 전달되어야 할까요?

 시그널이 적용되는 스레드에 시그널을 전달합니다.

 프로세스 내의 모든 스레드에 시그널을 전달합니다.

 프로세스 내의 특정 스레드에 시그널을 전달합니다.

 프로세스의 모든 시그널을 수신할 특정 스레드를 지정합니다.

스레드 취소란?

 작업이 완료되기 전에 스레드를 종료하는 것

• 예를 들어, 여러 스레드가 DB를 검색하고, 하나의 스레드가 결과를 반환합니다. 나머지 스레드는 최소될 수 있습니다.

취소되어야 할 스레드를 대상 쓰레드(target thread)라고 합니다.

 

❖ 두 가지 일반적인 방법:

 비동기 취소는 대상 스레드를 즉시 종료합니다.

 연기 취소는 대상 스레드가 주기적으로 취소 여부를 확인할 수 있도록 합니다.

❖ 비동기 취소

  •  취소하는 것에 어려움이 발생할 수 있는 상황
    • 취소된 스레드에 자원이 할당되어 있는 경우
    • 다른 스레드와 공유하는 데이터를 업데이트 중인 동안에 스레드가 취소되는 경우

❖ 연기 취소

  • 한 스레드가 대상 스레드가 취소되어야 한다는 것을 나타냅니다.
  • 취소는 대상 스레드가 취소되어야 하는지 여부를 확인하는 플래그를 확인한 후에만 발생합니다.

❖ 스레드 지역 저장소 (TLS)는 각 스레드가 고유한 데이터 복사본을 가질 수 있도록 합니다. (프로세스 내의 다른 스레드와 공유되지 않음)

 스레드 생성 과정을 제어할 수 없을 때 유용합니다. (예: 스레드 풀 사용 시)

 

❖ 지역 변수와는 다릅니다.

• 지역 변수는 단일 함수 호출 동안에만 사용되며 함수 호출간에는 볼 수 없습니다.

• TLS는 함수 호출 간에도 사용할 수 있습니다.