본문 바로가기

Operating System [OS]

[OS] 06. Threads & Concurrency

Multithreading

Multithreaded Architecture

  • 대부분의 현대 애플리케이션은 멀티스레드임
  • 스레드는 애플리케이션 안에서 실행
  • 애플리케이션의 여러 작업은 별도의 스레드에서 실행 가능
  • 화면 업데이트 / 데이터 가져오기(Fetch) / 스펠(Spell) 체크 / 네트워크 요청 응답 등
  • 프로세스 생성은 무거운 작업(heavy-weight)이지만 스레드 생성은 가벼운 작업(light-weight)
  • 스레드는 일부의 레지스터와 스택만 따로 가지고 코드와 데이터는 공유함
  • 코드를 간단하게 할 수 있고 효율성을 높일 수 있음
  • 커널은 일반적으로 멀티스레드로 동작

Multithreading의 장점

  • Responsiveness 응답성 : 프로세스의 일부가 차단(block)된 상태일 때도 실행을 계속하는 것을 허용 / 특히, 사용자 인터페이스에 중요
  • Resource Sharing 자원 공유 : 스레드는 프로세스의 자원을 공유 / Shared Memory 혹은 메시지 패싱보다 쉬움
  • Economy 경제성 : 스레드 생성은 프로세스 생성보다 저렴 / 스레드 스위칭 오버헤드가 Context Switching 오버헤드보다 적음
  • Scalability 확장성 : 프로세스는 멀티프로세서 아키텍처의 장점을 활용할 수 있음

Multicore Programming

Multicore 혹은 Multiprocessor 시스템은 프로그래머에게 다음의 도전과제에 대한 압박을 줌

  • Task dividing activities 작업 분할 활동
  • Load balancing 부하 분산
  • Data splitting 데이터 분할
  • Minimizing data dependency 데이터 종속 최소화
  • Testing and debugging 테스트 및 디버깅

Parallelism 병렬성 : 시스템이 여러 작업을 동시에 수행할 수 있음

  • Data parallelism : 같은 데이터 집합을 여러 코어에 분산, 각각 동일한 작업을 실행
  • Task parallelism : 스레드를 코어에 분산, 각 스레드가 고유한 작업을 실행

Concurrency 병행성 : 여러 작업을 지원 / 단일 프로세서, 코어의 경우 스케줄러가 동시성을 제공

 

Amdahl’s Law


시리얼과 병렬 구성을 모두 가지는 애플리케이션에 추가적인 코어를 추가하면서 얻을 수 있는 성능을 식별
S = Serial Portion (시리얼 부분)

N = Processing Core 수
만약 앱이 75% 병렬 / 25% 시리얼이면 코어가 1개에서 2개가 되면 Speedup은 1.6배가 됨
N이 무한대에 가까워지면 속도 향상은 1/S로 수렴
앱의 시리얼 부분은 추가적인 코어를 추가했을 때 얻는 성능에 불균형한 영향을 미침


User Threads / Kernel Threads

User Threads : 사용자 수준 스레드 라이브러리에서 관리
3가지 주요 라이브러리 : POSIX Pthreads / Windows threads / Java threads

 

Kernel Threads : 커널에 의해 지원
사실상 모든 범용 OS : Windows / Solaris / Linux / Mac OS X


사용자 스레드와 커널 스레드 사이의 멀티스레딩 모델

  • Many-to-One Model - 여러 사용자 수준 스레드가 하나의 커널 스레드에 매핑
     - 하나의 스레드가 Block되면 모든 스레드가 Block됨
     - 멀티코어 시스템에서 멀티스레드가 병렬로 작동하지 않을 수 있음
       한번에 커널에 1개만 존재할 수 있기 때문
     - 이 모델은 현재 거의 사용되지 않음
     - 예시 : Solaris Green Thread Library / Java 초기 버전 스레드
  • One-to-One Model - 각 사용자 수준 스레드는 커널 스레드와 1:1로 매핑
     - 사용자 수준 스레드 생성은 커널 스레드를 생성함
     - Many-to-One보다 더 많은 병행성(Concurrency)를 제공
     - 프로세스당 스레드의 수가 오버헤드로 인해 제한될 수 있음
     - 예시 : Windows / Linux / Solaris 9 이후 (최근 가장 많이 사용)
  • Many-to-Many Model - 정확히는 여러 사용자 수준 스레드가 그보다 적은 수의 커널 스레드와 매핑
     - OS가 충분한 수의 커널 스레드를 생성하도록 함
     - 애플리케이션은 원하는 수만큼의 사용자 스레드를 생성하고
     이들에 대응하는 커널 스레드들이 다중 처리기에서 병렬로 수행될 수 있음
     - Many-to-One과 One-to-One의 장점을 취한 접근법
  • Two-level Model - Many-to-Many와 유사하지만 하나의 사용자 스레드가 하나의 커널 스레드에 바인딩하는 것을 허용
     - Many-to-Many와 One-to-One의 혼합
     - 일반적으로 사용자 스레드와 커널 스레드 사이에는 중간 데이터 구조를 사용 = Lightweight Process(LWP)

LWP는 프로세스가 사용자 스레드를 실행하도록 스케줄할 수 있는 가상 프로세서처럼 보임
각 LWP는 커널 스레드에 연결되어 있음


Explicit Threading

Thread Library : Pthreads

  • 스레드 생성과 동기화를 위한 POSIX 표준 API
  • Specification(명세), not Implementation(구현)
  • API는 스레드 라이브러리의 동작을 지정
  • 구현은 라이브러리의 개발(development)에 달려 있음
  • UNIX OS에 흔히 사용됨 (Solaris, Linux, Mac OS X)

Windows Threads

 

Java Threads

  • 자바 스레드는 JVM에 의해 관리
  • 스레드 클래스를 확장하는 방법
  • Runnable 인터페이스를 구현하는 방법

Implicit Threading

스레드 수 증가의 인기가 높아지고, 명시적인 스레드로 프로그램의 정확성을 유지하는 것이 어려워짐
스레드의 생성과 관리는 프로그래머보단 컴파일러와 run-time 라이브러리에 의해 수행

  • Thread Pools
     - 작업을 대기하면서 Pool에 여러 스레드를 생성
     - 장점
       1. 새로운 스레드를 생성하는 것보다 존재하는 스레드로 요청을 처리하는 것이 약간 더 빠름
       2. 애플리케이션의 스레드 수를 Pool의 크기에 바인딩할 수 있게 허락
       3. 생성하는 작업에서 수행하는 작업을 분리함으로써 작업 실행을 위한 다양한 전략을 허용
           (작업은 주기적으로 실행되도록 스케줄될 수 있음)
  • OpenMP
     - C 및 C++을 위한 컴파일러 지시문(directives)과 API의 집합
     - Shared-memory 환경에서 병렬 프로그래밍을 지원
     - 병렬 영역(parallel regions)을 식별 : 병렬로 실행할 수 있는 코드의 블록

Thread Related Issues

fork( ), exec( ) 시스템 콜
멀티스레드 프로그램에서 프로세스를 복제/생성하는 fork( ) 시스템 콜을 실행하면 새 프로세스는 모든 스레드를 복제? 혹은 메인 스레드를 가지는 하나만 복제? -> 몇몇 UNIX 시스템에서는 2가지 버전을 모두 지원

 

exec( ) 시스템 콜은 기존 스레드를 모두 무시하고 새로운 프로그램으로 대체

  • fork( ) 이후에 exec( )가 호출되면 모든 스레드를 복제할 이유가 없음
  • fork( ) 이후에 exec( )가 호출되지 않는다면, 모든 스레드의 복제가 의미 있음

Signal Handling 시그널 처리

  • 시그널 : 프로세스에게 어떤 사건이 일어났음을 알리기 위해 사용
  • Synchronous Signal : 어떤 사건을 처리하기 위해 발생 (예 : 불법적 메모리 접근 / 0으로 나누기)
  • Asynchronous Signal : 임의 시점에 발생 (예 : 인터럽트(Ctrl-C), 타이머 만료)
  • 동기식, 비동기식 둘 다 다음과 같이 전달
     - 시그널은 특정 사건이 일어나야 생성
     - 시그널은 생성되면 프로세스에게 전달
     - 시그널이 전달되면 반드시 처리(Handling)되어야 함
  • 모든 시그널은 처리기(Handler)에 의해 처리
     - 디폴트 시그널 처리기 : 시그널마다 기본적인 처리 방법을 정의
     - 사용자 정의 시그널 처리기 : 사용자가 시그널 처리 방법을 정의
  • 단일 스레드 프로그램에서 시그널은 프로세스 레벨에서 처리됨
     - 시그널을 받으면, 프로세스 수행을 중단 후, 시그널 처리기에서 해당 시그널을 처리한 후 다시 프로세스 수행 재개
  • 멀티스레드 프로그램에서 시그널 처리는 다소 복잡
     - 가능한 선택
       1. 시그널이 적용될 스레드에게 전달 (특정 스레드가 시그널 처리기를 등록한 경우) - 모든 스레드에게 전달
       2. 몇몇 스레드에게만 선택적으로 전달
       3. 특정 스레드가 모든 신호를 전달받도록 지정

Thread Cancellation

  • 스레드가 완료되기 전에 종료되는 것
    예 : 여러 스레드가 DB를 검색하다, 한 스레드가 원하는 결과를 찾았다면 나머지 스레드는 취소
    예 : 웹브라우저가 페이지를 적재하는 도중, 사용자가 Stop 버튼을 눌렀다면 관련 스레드들은 취소
  • 취소될 스레드는 target thread라고 함
  • Asynchronous cancellation : 타겟 스레드를 즉시 종료함
  • Deferred cancellation : 타겟 스레드가 주기적으로 자신이 취소되어야 하는지 확인할 수 있도록 함
  • Pthread 코드를 이용하여 스레드를 생성하고 취소함
  • 스레드 취소 요청이 발생해도, 실제 취소는 스레드의 상태에 따라 결정
    만약 스레드 취소가 비활성화된 경우, 취소는 활성화될 때까지 대기(pending) 상태로 유지됨
    Default Type은 Deferred임
  • 취소는 스레드가 취소 포인트(cancellation point)에 도달했을 때만 발생
    예 : pthread_testcancel( ) - 그리고 cleanup handler가 발생
  • 리눅스 시스템에서는 스레드 취소는 시그널을 통해 처리됨

Thread Cancellation (Cont.)

Thread-Local Storage (TLS)

  • Thread-Local Storage는 각 스레드가 자신의 데이터 복사본을 가질 수 있게 하는 것
  • 스레드 생성 프로세스를 제어하지 않을 때 유용 (예 : 스레드 Pool을 이용할 때)
  • 로컬 변수와의 차이점
     - 로컬 변수는 단일 함수 호출 동안에만 볼 수 있음
     - TLS는 함수 호출 간에도 보임
  • Static 데이터와 유사
  • TLS는 각 스레드에 고유함

'Operating System [OS]' 카테고리의 다른 글

[OS] 08. Deadlocks  (1) 2024.01.02
[OS] 07. Synchronization Tools  (1) 2024.01.02
[OS] 05. Virtual Memory  (2) 2024.01.02
[OS] 04. Main Memory  (2) 2024.01.02
[OS] 03. CPU Scheduling  (3) 2024.01.01