운영체제 요약 - 4. 스레드

4.1. 개요(Overvies)

스레드는 CPU 이용의 기본 단위이다. 각 스레드는 서로 다른 스레드 ID, 프로그램 카운터, 레지스터, 스택을 가지고 있다. 같은 프로세스에 속한 스레드는 코드, 데이터 섹션, 파일과 같은 자원들을 공유한다.

스레드들은 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드 생성시간이나 문맥 교환 시간이 프로그램끼리 보다 빠르다. 그리고 각각의 스레드들은 다중 처리기가 있을 경우, 서로 다른 처리기에서 병렬로 수행될 수 있다.

4.2. 멀티코어 프로그래밍(Multicore Programming)

4.2.1. 프로그래밍 도전과제

  1. 테스크 인식(Identifying tasks)
    • 테스크를 독립된 병행가능 테스크로 나눌 수 있는 영역을 찾아야 한다.
  2. 균형(Balance)
    • 찾아진 부분들이 전체 작업에 균등한 기여도를 가지도록 나눠야 한다.
  3. 데이터 분리(Data spliting)
    • 접근하는 데이터 또한 개별 코어에서 사용할 수 있도록 나눠야 한다.
  4. 데이터 종속성(Data dependency)
    • 데이터는 둘 이상의 테스크사이에 종속성이 없어야 한다.
    • 만약 있다면, 동기화를 수행해야 한다.
  5. 시험 및 디버깅(Testing and debugging)
    • 병렬로 실행되면 다양한 실행 경로가 있을 수 있다.

4.3. 멀티스레드 모델(Multithreading Models)

사용자 스레드와 커널 스레드는 연관 관계가 존재한다.

4.3.1. 다대일 모델(Many-to-One Model)

한 번에 하나의 스레드만이 커널에 접근할 수 있다. 멀티스레드가 멀티코어 시스템에서 병렬로 수행 될 수 없기에 쓰이지 않는다.

4.3.2. 일대일 모델(One-to-One Model)

사용자 스레드당 하나의 커널 스레드가 연결된다. 멀티코어에서 멀티스레드가 병렬로 수행될 수 있지만, 사용자 스레드가 생성될 때마다 커널 스레드를 생성해야 한다.

4.3.3. 다대다 모델(Many-to-Many Model)

여러 개의 사용자 스레드를 그보다 작거나 같은 수의 커널 스레드와 연결시킨다. 어느 정도 병렬 수행될 수 있으며, 한 스레드가 동기식 시스템 호출을 했을 때, 다른 스레드의 수행을 스케줄 할 수 있다.

4.4. 스레드 라이브러리(Threads Library)

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

라이브러리 구현에는 주된 두 가지 방법이 있다.

  1. 커널의 지원이 없는 사용자 공간의 라이브러리
    • 코드와 자료 구조가 사용자 공간에 존재
    • 라이브러리 함수 호출은 시스템 호출이 아니라 사용자 공간의 지역 함수 호출
  2. 운영체제에 의해 지원되는 커널 수준 라이브러리
    • 코드와 자료 구조가 커널 공간에 존재
    • 라이브러리 API호출은 시스템 호출

다수의 스레드를 생성하는 일반적인 두 가지 방법은 비동기, 동기 스레딩이다. 비동기 스레딩은 부모가 자식 스레드를 생성한 후 병렬 수행된다. 반면, 동기 스레딩은 부모가 자식 스레드 모두가 종료될 때까지 기다렸다가 실행을 재개한다.

4.5. 암묵적 스레딩(Implict Threading)

멀티스레드 응용의 설계를 컴파일러와 런타임 라이브러리가 도와주는 암묵적 스레딩이 있다. OpenMP, Grand Central Dispatch, 스레드 풀 등 많은 기술들이 있다.

특히 스레드 풀은 웹 서버에서 최대 스레드 수, 스레드 생성 시간을 해결하는데 도움이 된다. 미리 일정한 수의 스레드들을 생성하여, 요청이 들어오면 스레드를 할당하고 끝나면 반환한다. 스레드가 전부 할당되었다면 다른 스레드가 반환될 때까지 기다려야 한다.

4.6. 스레드와 관련된 문제들(Threading Issues)

4.6.1. Fork() 및 Exec() 시스템 호출

멀티스레드 프로그램에서 fork() 시스템 호출은, 이를 호출한 스레드 하나만 복사한 프로세스 생성 혹은 스레드 전부를 복사한 프로세스 생성이 가능하다.

exec() 시스템 호출은 모든 스레드를 삭제하고 지정한 프로그램으로 대체한다. 따라서 fork() 후 바로 exec()을 부른다면, 스레드 하나만 복사하는 fork()를 실행하는 것이 좋다.

4.6.2. 신호 처리(Signal Handling)

신호는 프로세스에게 어떤 사건이 일어났음을 알려준다. 불법 메모리 접근, 0으로 나누기 등이 있다.

모든 신호마다 커널이 실행시키는 디폴트 신호 처리기가 있다. 이는 사용자 정의 처리기에 의해 대체될 수 있다.

단일 스레드 프로그램에서 신호 처리는 항상 프로세스에게 전달된다. 하지만 여러 스레드가 한 프로세스에 있을 경우 어느 스레드에게 신호를 전달할 지는 복잡하다.

선택에는 신호를 만든 스레드, 모든 스레드, 몇몇 스레드, 특정 스레드 등이 있다.

4.6.3. 취소(Cancellation)

다음과 같은 두 가지 방식으로 스레드가 끝나기 전에 강제로 종료시킬 수 있다.

  1. 비동기식 취소
    • 스레드를 즉시 강제 종료시킨다.
    • 시스템 자원을 회수하지 못할 수도 있다.
  2. 지연 취소
    • 스레드가 종료되기 전에 자원 상태를 점검한다.

4.6.4. 스레드 로컬 저장소(Thread-Local Storage, TLS)

한 프로세스에 속한 스레드들은 프로세스의 데이터를 공유한다. 하지만 각 스레드가 자신만의 데이터를 가져야 할 필요도 있다. 이를 TLS라 부른다.

4.6.5. 스케줄러 액티베이션(Scheduler Activations)

다대다 모델에는 사용자 스레드와 커널 스레드의 통신 문제가 있다. 이를 해결하기 위한 한 방법은 경량 프로세스(Light Weight Process, LWP)를 사용하는 것이다.

사용자 스레드 라이브러리에게 있어, LWP은 응용이 사용자 스레드를 스케줄할 수 있는 가상 처리기처럼 보인다. 커널은 관여하지 않는 것처럼 보인다.

LWP는 하나의 커널 스레드에 부속되어 있으며, 사용자 스레드와도 연결되어 있다. 물리 처리기에서 스케줄 하는 대상은 커널 스레드이다. 커널 스레드가 입출력같은 것을 동기식으로 기다리면 LWP가 기다리고, 이에 연결된 사용자 스레드도 기다린다.

사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중 하나는 스케줄러 액티베이션이다. 커널은 응용 프로그램에게 가상 처리기 집합(LWP)을 제공한다. 응용 프로그램을 이를 통해 사용자 스레드를 스케줄 한다.

커널은 upcall 이라 불리는 프로시저를 통하여 응용 프로그램에게 사건을 알린다. 사건은 응용 프로그램의 스레드가 봉쇄(동기)하려고 할 때 발생한다. upcall은 가상 처리기에서 수행되는, 스레드 라이브러리의 upcall 처리기에 의해 처리된다.

upcall 처리는 다음과 같이 수행된다.

  1. 커널이 스레드가 봉쇄하려고 하는 것과 스레드의 식별자를 알려주는 upcall을 한다.
  2. 커널이 새로운 가상 처리기를 응용 프로그램에게 할당한다.
  3. 응용 프로그램이 새로운 가상 처리기에서 upcall 처리기를 수행한다.
  4. upcall 처리기는 봉쇄 스레드의 상태를 저장하고 원래 가상 처리기를 반환한다.
  5. upcall 처리기는 새로운 가상 처리기에서 응용 프로그램의 실행 가능한 스레드를 스케줄 한다.
  6. 커널이 봉쇄가 풀리는 사건을 감지하고 upcall을 스레드 라이브러리에게 한다.

댓글남기기