공룡책 4장 : 다중 코어 프로그래밍

bo-yoon·2022년 3월 15일
0

os

목록 보기
1/1

1. 개요

스레드

  • CPU 이용의 기본 단위
  • 프로세스의 작업 흐름
  • 스레드 ID, 프로그램 카운터(PC), 레지 스터 집합, 그리고 스택으로 구성되어 있음
  • 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일, 신호 같은 운영체제
  • 전통 적인 프로세스는 하나 이상의 제어 쓰레드를 가짐

싱글 스레드

하나의 프로세스가 한번에 하나의 작업만 수행

멀티 스레드

하나의 프로세스가 동시에 여러 작업을 수행

동기

  • 현대 컴퓨터와 모바일 기기에서 작동하는 거의 모든 소프트웨어는 멀티 스레드 사용

장점

  1. 응답성 : 대화형 응용을 다중 스레드화하면 응용 프로그램의 일부분이 봉쇄되거나, 응용 프로그램이 긴 작업을 수행하더라도 프로그램이 계속되는 것을 허용하므로써, 사용자에 대한 응답성을 증가시킨다.
  2. 자원 공유 : 스레드는 자동으로 그들이 속한 프로세스의 자원들과 메모리를 공유한다.
  3. 경제성 : 스레드의 문맥 교환은 프로세스 사이서 보다 빠르고, 스레드 생성이 프로세스 생성보다 경제적이다.
  4. 규모 적응성 : 각각의 스레드는 다른 처리기에서 병렬로 실행

2. 다중 코어 프로그래밍

다중 코어 : 단일 컴퓨팅 칩에 여러 컴퓨터 코어를 배치

단일 코어 → 하나의 스레드만 실행

다중 코어 → 각각의 코어에 별도의 스레드 실행

동시성(concurrency) vs 병렬성(parallelism)

동시성 → 모든 작업이 진행되게 하여 둘 이상의 작업을 지원한다.

병렬성 → 둘 이상의 작업을 동시에 수행

다중 프로그래밍을 하기 위해 극복해야 할 5가지 과제

  1. 테스크 인식 : 응용을 분석하여 독립된 병행 가능 태스크로 나눌 수 있는 영역을 찾는 작업이 필요하다.
  2. 균형 : 전체 작업에 균등한 기여도를 가지도록 태스크로 나누는 것도 중요
  3. 데이터 분리 : 태스크가 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 함
  4. 데이터 종속성 : 태스크가 접근하는 데이터는 둘 이상의 태스크 사이에 종속성이 없는지 검토되어야 함
  5. 시험 및 디버깅 : 프로그램이 다중 코어에서 병렬로 실행 될때, 다양한 실행 경로가 존재할 수 있다.

→ 따라서 다중 코어 시스템을 기반으로 소프트웨어 설계하기 위해서는 새로운 접근법이 필요

병렬 실행의 유형

종류 2가지

  1. 데이터 병렬 실행
    • 동일한 데이터의 부분 집합을 다수의 계산 코어에 분배한 뒤 각 코어에서 동일한 연산을 실행하는 데 초점을 맞춘다.
  2. 태스크 병렬 실행
    • 태스크를 다수의 코어에 분배한다.

3. 다중 스레드 모델

스레드를 위한 자원은 사용자 스레드를 위해서는 사용자 수준에서, 또는 커널 스레드를 위해서는 커널 수준에서 제공된다.

사용자 스레드 : 커널 위에서 지원되며 커널의 지원 없이 관리

커널 스레드 : 운영체제에 의해 직접 지원되고 관리됨

다대 일 모델 (Many to One Model)

  • 많은 사용자 수준 스레드를 하나의 커널 스레드로 연결한다.
  • 스레드 관리는 스레드 라이브러리에 다루어져 효율적이다.
  • 하지만 한 스레드가 봉쇄형 시스템 콜을 할 경우 전체 프로세스가 봉쇠된다.
  • 또한 한번에 하나의 스레드 만이 커널에 접근할 수 있기 때문에, 다중 스레드가 다중 코어 시스템에서 병렬로 실행될 수 있다.
  • 예) 그린 스레드

일 대 일 모델 (One to One Model)

  • 각 사용자 스레드를 각각 하나의 커널 스레드로 연결
  • 다대일 모델보다 많은 병렬성 제공
  • 다중 처리기에서 다중 스레드가 병렬로 수행되는 것을 허용
  • 단, 사용자 스레드를 만들려면 해당 커널스레드를 만들어야 하며 많은 수의 커널 스레드가 성능에 부담을 줄수 있다는 것
  • 예 ) 리눅스

다 대 다 모델 (Many to Many Model)

  • 여러 개의 사용자 수준 스레드를 그보다 작은 수, 혹은 같은 수의 커널 스레드로 멀티 플렉스
  • 커널 스레드의 수는 으용 프로그램이나 특정 기계에 따라 결정 ( 응용 프로그램은 4개 코어 시스템 보다 8개 코어 시스템에서 더 많은 커널 스레드를 할당 받을 수 있다.)
  • 개발자는 필요한 만큼 많은 사용자 수준 스레드를 생성할 수 있다. 그리고 그에 상응하는 커널 스레드가 다중 처리기에서 병렬로 수행될 수 있다.
  • 또한, 스레드가 봉쇄형 시스템 콜을 발생시켰을 대, 커널이 다른 스레드의 수행을 스케줄링 할 수 있다.
  • two-level model
    • 많은 사용자 스레드를 적거나 같으 수의 커널 스레드로 멀티플렉스 시키지만 한 사용자 스레드가 하나의 커널 스레드에만 연결되는 것을 허용한다.
    • 다대다 모델의 변형

4. 스레드 라이브러리

  • 스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하기 위한 API 를 제공한다.
  • 구현 방법 1) 커널의 지원 없이 완전히 사용자 공간에서만 라이브러리를 제공하는 것 = 사용자 공간의 지역함수를 호출 2) 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현하는 것 = 커널 시스텀 콜을 호출
  • 현재 사용하는 종류 3가지 1) POSIX Pthread - 사용자 또는 커널 수준 라이브러리 2) Windows - 윈도우 3) JAVA - 자바용
    • 모든 자바 프로그램은 적어도 하나의 단일 스레드로 수행된다. JAVA 스레드는 JVM 을 제공하는 어떤 시스템에서도 사용할 수 있다.

    • 스레드를 명시적으로 생성하는 방법

      1) Thread 클래스에서 파생된 클래스를 만들고 run() 메소드를 재정의
      
      2) Runnable 인터페이스를 구현하는 클래스를 정의하여 run() 메서드를 정의
      
      ```java
      class Task implements Runnable {
      		public void run() {
      				System.out.println("I am a thread. ");
          }
      }
      
      ..
      
      Thread worker= new Thread(new Task());
      worker.start();
      
      ```

      Thread 객체에 대해 start() 메소드를 호출하면 2가지 작업이 실행된다.

      1) 메모리가 할당되고, JVM 내에 새로운 스레드가 초기화된다.

      2) run() 메소드를 호출하면 스레드가 JVM 에 의해 수행될 자격을 갖게 한다. run() 메소드를 직접 호출하지마라

  • 비동기와 동기 스레딩의 전략
    • 비동기 스레딩 : 부보가 자식 스레드를 생성한 후 부모는 자신의 실행을 재개하여 부모와 자식 스레드가 서로 독립적으로 병행하게 실행한다. 두 스레드 간의 데이터 공유는 없다.
    • 동기 스레딩 : 부모 스레드가 하나 이상의 자식 스레드를 생성하고 자식 스레드 모두가 종료할때까지 기다렸다가 자신의 실행을 재개하는 방식

5. 암묵적 스레딩

  • 다중 코어 처리의 지속적 성장에 따라 수백 또는 심지어 수천 개의 스레드를 가진 응용이 등장 → 설계가 어려움

암묵적 스레딩이란?

  • 스레딩의 생성과 관리 책임을 응용 개발자로부터 컴파일러와 실행시간 라이브러리에게 넘겨주는 것
  • 암묵적 스레딩을 사용해서 다중 코어 처리기를 활용할 수 있는 4가지 접근법

1) 스레드 풀

  • 서비스할 때마다 스레드를 생성하는 데 소요되는 시간이 걸린다. 근데 이 스레드는 곧 폐기 된다.
  • 또한 동시에 실행할 수 있는 최대 스레드 수가 몇 개 까지 가능한지도 설계해야 한다.
  • 이런 문제를 해결 할 수 있는 것이 스레드 풀이다.
  • 스레드 풀은 아예 일정한 수의 스레드들을 미리 풀로 만들어 놓는다. 스레드는 평소에는 idle 하게 대기 하고 있다가 서버가 요청을 받으면 스레드 풀에 제출하고 풀에 사용 가능한 스레드가 있으면 깨어나고 요청이 즉시 서비스 된다.
  • 장점
    • 새 스레드를 만들어 주기 보다 기존 스레드로 서비스 하는 것이 종종 더 빠르다.
    • 스레드 풀은 임의 시각에 존재할 스레드 개수에 제한을 둔다. 이러한 제한은 많은 수의 스레드를 병렬 처리할 수 없는 시스템에 도움이 된다.
    • 태스크를 생성하는 방법을 태스크로 부터 분리하면 태스크를 실행을 다르게 할 수 있다.
    • 이러한 구조는 시스템 부하가 적을 때에는 더 작은 풀을 유지하도록 하므로써 메모리 등의 소모를 더 줄일 수 있다.

JAVA 스레드 풀

  • java.util.concurrent 패키지에는 여러 종류의 스레드 풀에 대한 API 가 제공된다.
  • 여기서는 3가지 모델에 집중한다.
    • 단일 스레드 Executor, newSingleThreadExecutor() 는 크기가 1인 풀을 생성한다.
    • 고정 스레드 Executor, newFixedThreadPool(int size) 는 지정된 수의 스레드가 있는 스레드 풀을 생성
    • 캐시 스레드 Executor, newCachedThreadPool() 많은 경우 스레드를 재사용하는 무제한 스레드 풀을 생성

2) Fork Join

  • 메인 부모 스레드가 하나 이상의 자식 스레드를 생성한 다음 자식 종료를 기다린 후 join 하고 그 시점부터 자식의 결과를 확인 하고 결합할 수 있다.
  • 이 동기식 모델은 종종 명시적 스레드 생성이라고 특정 지어지지만 암시적 스레딩에도 사용될 수 있다.

3) OpenMP

  • c, c++ 또는 Fortran 으로 작성된 API와 컴파일러 디렉티브의 집합

4) Grand Central Dispatch

  • macOS, ios 용 기술

6. 스레드와 관련된 문제들

1) Fork() 및 Exec() 시스템 콜

: 다중 스레드 프로그램에서는 fork() 와 exec() 의 의미가 달라질 수 있다. 만일 한 프로그램의 스레드가 fork() 를 호출하면 새로운 프로세스는 모든 스레드를 복제해야 하는 가 아니면 한 개의 스레드만 가지는 프로세스여야 하는가?

→ 그것은 응용 프로그램마다 다르다. 만약 fork() 를 부드자마자 다시 exec을 부른다면 모든 스레드를 다 복제해서 만들어주는 것은 불필요하다. 왜냐하면 exec에서 지정한 프로그램이 곧 모든것을 다시 대체할 것이기 때문이다. 하지만 fork() 후 exec() 를 하지 않는 다면 새 프로세슨느 모든 스레드를 복제해야한다.

2) 신호처리

: 신호는 알려줄 이벤트의 근원지나 이유에 따라 동기식 또는 비동기식으로 전달 될 수있다.

  • 동기식 신호의 예 : 동기식 신호는 신호를 발생시킨 연산을 수행한 동일한 프로세스에 전달된다.
    • 불법적인 메모리 접근
    • 0으로 나누기

3) 스레드 취소

: 스레드가 끝나기 전에 그것을 강제 종료시키는 작업.

예) 여러 스레드가 데이터 베이스를 병렬로 검색하고 있다가 한 스레드 결과를 찾았다면 나머지 스레드는 취소되어도 된다.

이처럼 취소되어야 할 스레드를 목적 스레드(target thread) 라고 한다.

취소는 다음 두방법으로 가능하다.

1) 비동기 취소 : 한 스레드가 즉시 목적 스레드를 강제 종료시킨다.

2) 지연 취소 : 목적 스레드가 주기적으로 자신이 강제 종료되어야 할지를 점검. 이경우 목적 스레드가 질서정연하게 강제 종료될 수 있는 기회가 만들어진다.

→ 스레드 취소에서 가장 어려운것은 할당된 자원할당 문제이다.

→ 스레드가 다른 스레드와 공유하는 자료 구조를 갱신하는 도중에 취소요청이 온다면?

7. 운영체제 사례

리눅스 스레드

프로세스를 복제하는 기능을 가진 fork() 시스템 콜을 제공

clone() 시스템 콜을 이용하여 스레드를 생성할 수 있는 기능도 제공

그러나 리눅스는 프로세스와 스레드를 구별하지 않는다. 보통 태스크라는 용어를 더 사용한다.

profile
개발 로그 🍎 🍎 🍎

0개의 댓글