Post

Ch4. Threads and Concurrency

Ch4. Threads and Concurrency

목표

  • 스레드의 기본 구성, 프로세스와의 비교
  • 멀티스레드 어플리케이션 설계의 이점과 첼린징
  • 스레드 풀, fork-join, Grand Central Dispatch를 포함한 암시적 스레딩(implicit threading)다양한 접근 방식
  • 리눅스 운영체제에서 스레드를 어떻게 사용하는지

왜 스레드를 사용하는가?

최근의 어플리케이션은 대부분이 멀티스레드로 동작한다. 스레드는 어플리케이션 안에서 동작한다. 멀티테스크는 어플리케이션에서 다음 스레드들을 이용해서 구현되었다.

  • 화면 갱신
  • 데이터 fetch
  • 맞춤법 확인
  • 네트워크 요청 확인 등 프로세스는 무거운 방면에 스레드는 비교적 가볍게 동작한다. 코드가 간단해지고, 효율성이 좋아진다. 커널은 일반적으로 멀티스레드이다.

그럼 스레드는 어떻게 생겼나?

여러 스레드는 같은 프로세스 안에서 실행된다. 이때, 각 스레드는 공뷰하는 부분과 독립적으로 가지고 있는 부분으로 나뉜다.

  • 공유 자원

    • code : 모든 스레드는 같은 코드를 실행함
    • data : 같은 전역 변수나 static 변수
    • heap : 동적할당
    • files : 열려 있는 파일 디스크립터 등
  • 독립 자원
    • registers : 각 스레드 CPU 사용 시 자기만의 레지스터가 필요
    • stack : 함수 호출 기록이 다름 (지역변수 등)
    • PC : 각 스레드는 다른 위치에서 실행 가능함

      멀티스레드 서버 아키텍처

      장점 1. Responsiveness (응답성) : 한 부분이 block되어도 다른 스레드는 계속 실행할 수 있어서 사용자 인터페이에 우리 2. Resource Sharing (자원 공유) : 스레드는 같은 프로세스 내의 메모리와 자원을 공유하므로, 통신이 빠르고 효율적 3. Economy (경제성) : 스레드 생성은 프로세스 생성보다 비용이 훨씬 적음 4. Scalability (확장성) : 멀티코어 시스템에서 여러 스레드가 병렬로 동작해 성능 향상 가능

멀티코어 프로그래밍

  • 멀티코어, 멀티프로세서 시스템 프로그래머한테는 여러 도전과제가 있음
    • 작업 분할
    • 균형
    • 데이터 분할
    • 데이터 의존
    • 테스트와 디버깅
  • 병렬성은 시스템이 동시에 여러 작업을 수행할 수 있음을 의미한다.
    • Data parallelism : 각 코어가 같은 데이터의 일부를 나눠서 같은 연산한다.
    • Task parallelism : 스레드를 여러 코어에 분산시키고, 각 스레드가 고유한 작업을 수행한다.
  • 동시성은 여러 작업이 진행 중일 수 있도록 지원한다.
    • 단일 프로세서/코어에서도, 스케줄러가 동시성을 제공할 수 있다

Amdahl’s Law

어플리케이션에서 병렬화되지 않는 부분이 전체 성능 향상에 큰 제약을 건다.

S : 전체 작업 중 직렬 부분 비율 N : 사용하는 코어 개수

한계점

코어 수 N이 무한대로 많아져도 속도는 1 / S 이상 올라갈 수 없음

User Threads 와 Kernel Threads

  • User Threads : user level에서 관리됨 (운영체제가 아닌 사용자 코드 or 라이브러리에서)
    • POSIX Phreads
    • windows threads
    • Java threads
  • Kernel Threads : 운영체제 커널에서 직접 지원 및 관리
    • 모든 주요 OS가 커널 스레드 사용함
      • Windows
      • Linux
      • macOS
      • iOS
      • Android

Multithreading Models

  • Many-to-One
    • 많은 유저레벨의 스레드들이 하나의 커널 스레드로 맵핑된다.
    • 장점 : 원하는 만큼 사용자 스레드를 생성할 수 있음
    • 단점
      • 하나의 스레드가 블로킹되면, 전체 사용자 스레드가 멈춤
      • 멀티코어 시스템이라도, 커널 스레드가 하나뿐이라 병렬 실행이 안됨
    • 단점이 너무 강하기 때문에 요새는 안
  • One-to-One
    • 각 유저레베의 스레드가 1:1로 커널 스레드에 맵핑된다.
    • 장점 :
      • 더 병행적이다.
      • 스레드가 블로킹되어도 다른 스레드를 실행할 수 있음.
      • 여러개의 스레드를 다중 코어에 매핑할 수 있음
    • 단점 :
      • 사용자 스레드와 커널 스레드가 1:1이기 때문에 사용자 스레드를 무한정 생성 할 수 없음
    • 현재 대부분의 운영체제가 사용하는 방심임
  • Many-to-Many
    • 사용자 스레드가 여러개의 커널 스레드에 매핑 가능
    • 장점:
      • 원하는 만큼 사용자 스레드 생성 가능
      • 멀티코어에서 병령 실행 가능
      • 유연한 매핑 구조 덕분에 효율적인 스케줄링 가능
    • 단점:
      • 구현이 복잡하고 어렵다
      • 멀티플렉싱하는 부분에서 병목현상이 있을 수 있음
    • 잘 사용하지 않음
  • Two-level
    • M:M과 1:1 방식을 섞어 놓은 형태
    • 대부분의 사용자 스레드는 커널 스레드와 다대다(M:N) 관계로 multiplexing됨
    • 그런데 특정 사용자 스레드 일부는 커널 스레드에 1:1로 고정해서 실행할 수도 있음
    • 즉, M:N과 1:1 모델이 혼합된 구조
    • 근데 구현이 복잡해서 잘 안씀

Thread Libraries

프로그래머가 스레드를 만들고 관리할 수 있게 해주는 API를 제공하는 도구 구현방식

  1. 유저 공간에서만 동작하는 라이브러리
    • 커널 개입 X
    • 성능 빠름
  2. 커널이 직접 지원하는 커널 레벨 라이브러리
    • 스레드 생성/종료 시 시스켐 콜 발생 -> 커널에 알려야 함
    • OS가 직접 스케줄리함 -> 병렬 실행 가능 동기 / 비동기 실행 차이 - Asynchronous threading : 부모와 자식 스레드가 독립적으로 실행 (각자 자기 일함) - Synchronous threading : 부모 스레드는 모든 자식 스레드가 끝날 때가지 기다려
라이브러리 설명
POSIX Pthreads 유저레벨 혹은 커널 레벨 (OS 따라)
Windows threads 커널 레벨 스레드 사용
Java threads OS에 따라 다름 (window에서는 커널)

Pthreads

사용자 레벨과 커널 레벨을 제공 스레드를 생성하고 비동기화하는 POSIX 표준 API 이다.

  • pthread_create : 스레드 생성함수
  • pthread_join : 스레드 종료 대기 함수
  • pthread_cancle
  • pthread_exit
  • pthread_kill : 시그널 생성 함수

Implicit Threading

쓰레드를 직접 생성해서 관리하는거는 굉장히 번거롭다. 개발자는 병렬로 수행할 수 있는 task를 식별하는데 주력하고, 스레드는 컴파일러나 런타임 라이브러리가 담당한다. 5가지 방법이 있다.

  • Thread Pools
    • 할 일을 대기하는 pool에 스레드를 생성한다.
    • 장점
      • 새롭게 스레드를 생성하는거보다 이미 존재하는 스레드에 요청하기 때문에 빠르다.
      • pool의 크기로 어플리케이션이 사용할 스레드의 수를 정할 수 있다.
      • task 실행과 생성을 분리하여 사용한다.
  • Fork-Join (OpenMp 에서 사용함)
    • 작업은 나누고 다시 모으는 구조가 명확해서 관리가 쉬움
    • 재귀적 문제에 많이 사용
  • OpenMP
    • 컴파일러 지시문을 사용
      • #pragma omp parallel : 코어 수 만큼 fork
      • #pragma omp parallel for : 코어의 수 만큼 스레드 생성하고 N개의 iteration을 스레드에 분할 매핑
  • Grand Central Dispatch (Apple’s thread pool)
    • Thread Pool의 크기를 자동 조절 하부는 POSIX pthread로 구현
    • 코드의 어느 부분을 병렬로 실행시킬 수 있는지 개발자가 지정할 수 있다.
    • GCD는 스레딩의 세세한 것을 관리
  • Intel Threading Building Blocks (C++ template library)

질문??

  • fork() 할 떄, threads 누글 복제할까?
    1. 현재 호출한 스레드만 복제할까?
    2. 프로세스 안에 있는 모든 스레드를 다 복제할까? -> 어떤 UNIX 시스템은 fork()의 두가지 버전을 제공하기도 한다. 근데 리눅스는 기본적으로 현재 호출한 스레드만 복제하는 방식 -> 바로 exec()를 해야 한전하게 프로그램을 교체할 수 있다. -> 근데 멀티 스레딩할때는 가급적이면 fork()를 사용하지 마라

Signal Handling

Signal은 Unix system에서 프로세스에 특정 이벤트가 발생해싿고 알려줄때 사용된다. Signal 처리하는 과정

  1. 특정 이벤트로 발생하여 생성된 signal
  2. 프로세스로에 전달된 signal
  3. Signal handling
    • Default handler
    • User-defined handler

      Thread Cancellation

      thread가 완전히 끝나기도 전에 강제로 종료시키는 것 - Asynchronous canellation -> 즉시 강제 종료해버림, 근데 자원 모두 해제 하지 못하고 메모리 누수 문제가 생길 수 있음 - Deferred cancellation -> 스레드가 주기적으로 자신이 취소인지 확 -> Cancellation point에서 스레드를 캔슬함. -> 일반적으로 read()와 같은 blocking system call이 해당 point가

Thread-Local Stroage (TLS)

각 스레드가 자기만의 복사본을 가지게 해주는 저장 공간

  • 언제 유용함?
    • 내가 스레드 생성 자체를 직접 제어할 수 없을 때
      • 스레드 풀을 사용할 떄 유용
  • TLS는 일반 지역 변수와 다름
    • TLS는 지역 변수와 다르게 함수가 끝나도 유지됨
    • 스레드별로 따로 존재함. 지역변수는 호출 스택에 따라 다
This post is licensed under CC BY 4.0 by the author.