운영체제 04

‘Operating System Concepts 8th’를 정리한 글이다.

4.1 개요

스레드는 CPU 이용의 기본 단위다. 스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택으로 구성된다.

스레드는 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일, 신호와 같은 운영체제 자원들을 공유한다.

image
단일 및 다중 스레드 프로세스
4.1.1 동기(Motivation)

많은 소프트웨어 패키지들은 다중 스레드를 이용한다.

예를 들어, 웹 브라우저는 이미지 또는 텍스트를 표시하는 하나의 스레드와 네트워크로부터 데이터를 가져오는 또 다른 스레드를 가질 수 있다. 워드프로세서는 그래픽을 표시하는 스레드, 입력 스레드, 철자/문법 체크하는 스레드를 가질 수 있다.

웹 서버에게 여러 개(수천 개쯤)의 클라이언트들이 동시에 접근할 때 단일 스레드 프로세스로 작동한다면, 클라이언트는 매우 긴 시간을 기다려야 한다.

하나의 해결책은 서버가 하나의 프로세스로 동작하게 하는 것이다. 서버에게 서비스 요청이 들어오면, 프로세스는 별도의 프로세스를 생성한다. 하지만 프로세스 생성 작업은 시간과 자원을 많이 사용하는 일이다. 새 프로세스가 해야 할 일이 기존 프로세스가 하는 일과 동일하다면 이 많은 오버헤드를 감수해야 할 이유는 없게 된다. 프로세스 안에 여러 스레드를 만들고 공유 할 수 있는 자원(스택을 제외한 데이터, 코드, 힙 등)을 공유하는 것이 효율적이다.

4.1.2 장점(Benefits)
  1. 응답성 : 대화형 응용을 다중 스레드화하면 응용 프로그램의 일부분이 봉쇄되거나, 응용 프로그램이 긴 작업을 실행하더라도 프로그램의 실행이 계속되는 것을 허용함으로써 사용자에 대한 응답성을 증가시킨다.

  2. 자원 공유 : 프로세스는 공유 메모리 또는 메시지 전달 기법을 통해서만 자원을 공유할 수 있지만 스레드는 자동적으로 그들이 속한 프로세스의 자원들과 메모리를 공유한다.

  3. 경제성 : 프로세스 생성을 위한 메모리와 자원 할당은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원을 공유하기에 스레드를 문맥 교환하는 것이 더 경제적이다.

  4. 규모 가변성 : 다중처리기 구조에서 더욱 이점이 증가할 수 있다. 다중처리기 구조(멀티코어)에서는 각각의 스레드가 다른 처리기에서 병렬로 실행될 수 있기 때문이다.

4.1.3 다중코어 프로그래밍

최근 시스템 설계는 하나의 칩 안에 여러 계산 코어를 넣는 것이다. 각 코어는 운영체제에게는 독립된 처리기로 보인다.

하나의 코어는 한 번에 오직 하나의 스레드만 실행하지만 여러 코어를 가진 시스템은 개별 스레드를 각 코어에 배정할 수 있기에 병행성은 스레드들이 병렬적으로 실행될 수 있다는 것을 뜻한다.

image
단일 코어 시스템에서의 병행 실행
image
다중코어 시스템에서의 병렬 실행

일반적으로 다중코어 시스템 상에서 프로그래밍을 하기 위해서는 5개의 극복해야 할 도전 과제가 있다.

  1. 작업 나누기 : 응용을 분석하여 각 태스크가 개별 코어 상에서 병렬적으로 실행될 수 있도록 응용을 독립된 병행가능 태스크로 나누는 작업을 말한다.

  2. 균형 : 각 부분들이 전체 작업에 균등한 기여도를 가지도록 나누는 것도 매우 종요하다.

  3. 데이터 분리 : 응용이 독립된 태스크로 나누어지는 것처럼 태스크가 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 나누어져야 한다.

  4. 데이터 종속성 : 태스크가 접근하는 데이터는 둘 이상의 태스크 사이에 종속성이 없는지 검토되어야 한다.

  5. 시험 및 디버깅 : 프로그램이 다중코어에서 병렬로 실행될 때 다양한 실행 경로가 존재한다. 그런 병행 프로그램을 시험하고 디버깅하는 것은 단일 스레드 응용을 시험하고 디버깅하는 것보다 내재적으로 훨씬 어렵다.

4.2 다중 스레드 모델(Multithreading Models)

사용자 스레드를 위해서는 사용자 수준에서, 커널 스레드를 위해서는 커널 수준에서 제공된다. 사용자 스레드는 커널 위에서 지원되면 커널의 지원 없이 관리되며 커널 스레드는 운영체제에 의해 직접 지원되고 관리된다.

사용자 스레드와 커널 스레드는 다음과 같은 방법의 연관 관계가 있다.

4.2.1 다대일 모델(Many-toOne Mode)

많은 사용자 수준 스레드를 하나의 커널 스레드로 사상한다. 스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다. 따라서 효율적이라 할 수 있지만, 한 스레드가 봉쇄형 시스템 호출을 할 경우 전체 프로세스가 봉쇄된다. 하나의 스레드만이 커널에 접근하기에 다중 스레드가 다중처리기에서 돌아도 병렬로 작동할 수 없다.

image
다-대-일 모델
4.2.2 일-대-일 모델(One-to-One Model)

각 사용자 스레드를 각각 하나의 커널 스레드로 사상한다. 이 모델은 하나의 스레드가 봉쇄적 시스템을 호출하더라도 다른 스레드가 실행될 수 있기에 다대일 모델보다 더 많은 병렬성을 제공한다.

이 모델의 단 하나의 단점은 사용자 수준 스레드를 생성할 때 그에 따른 커널 스레드를 생성해야 한다는 점이다. 생성할 때 발생하는 오버헤드로 인해 지원되는 스레드의 수를 제한한다. Windows 계열, Linux 계열의 운영체제들이 일대일 모델을 구현한다.

image
일-대-일 모델
4.2.3 다-대-다 모델(Many-to-Many Model)

여러 개의 사용자 수준 스레드를 그보다 적은 수 혹은 같은 수의 커널 스레도르 멀티플렉스 한다.

다대일 모델은 개발자가 원하는 만큼 사용자 수준 스레드를 생성하도록 허용하지만 하나의 스레드만이 커널에 의해서 스케줄되기에 진정한 동시성을 획득할 수 없다.

일대일 모델은 더 많은 병렬성을 제공하지만 개발자가 너무 많은 스레드를 생성하지 않도록 주의해야 한다.

다대다 모델은 이런 두 가지 단점을 어느정도 해결했다. 개발자는 필요한만큼 사용자 수준의 스레드를 생성할 수 있으며 상응하는 커널 스레드가 다중처리기에서 병렬로 실행될 수 있다.

image
다-대-다 모델

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

스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하기 위한 API를 제공한다. 스레드 라이브러리를 구현하는 데에는 두 가지 방법이 있다.

  • 커널의 지원없이 완전히 사용자 공간에서만 라이브러리를 제공한다. 이 라이브러리 함수를 호출하는 것은 시스템 호출이 아니라 사용자 공간의 지역 함수를 호출하는 것을 의미.

  • 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현한다. 이 경우 라이브러리를 위한 코드와 자료 구조는 커널 공간에 존재한다.

4.3.1 Pthreads

POSIX(IEEE 1003.1c)가 스레드 생성과 동기화를 위해 제정한 표준 API

4.3.2 Win32 스레드(Win32 Threads)

Win32 스레드 라이브러리를 이용하여 스레드를 생성한다. Win32 API에서 스레드는 CreateThread() 함수에 의해 생성된다.

4.3.3 Java 스레드(Java Thread)

Java 프로그램에서 스레드를 생성하는 기법은 Thread 클래스로부터 파생된 새로운 클래스를 생성하고 Thread 클래스의 run() 메서드를 override 하는 것이 있다. 다른 방법은(더 흔히 사용하는 방법) Runnable 인터페이스를 구현하는 클래스를 정의한다.

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

4.4.1 Fork() Exec() 시스템 호출

한 프로그램의 스레드가 fork()를 호출하면 새로운 프로세스는 모든 스레드를 복제해야 하는가 아니면 한 개의 스레드만 가지는 프로세스여야 하는가? 몇몇 UNIX 기종은 이 두 가지 버전 fork()를 다 제공한다. 하나는 모든 스레드를 복사하는 것과 다른 하나는 fork()를 호출한 스레드만 복제하는 것이다.

두 버전의 fork() 중 어느 쪽을 택할 것인지는 응용 프로그램에게 달려있다.

4.4.2 취소(Cancellation)

스레드 취소는 스레드가 끝나기 전에 강제 종료시키는 작업을 일컫는다. 예를 들어, 여러 스레드들이 데이터베이스를 병렬로 검색하다가 한 스레드가 결과를 찾았다면 나머지 스레드들은 취소되어도 된다.

이처럼 취소되어야 할 스레드를 목적 스레드(target thread)라고 부른다. 다음 두 가지 방식으로 취소할 수 있다.

  • 비동기식 취소(asynchronous cancellation) : 한 스레드가 목적 스레드를 즉시 강제 종료

  • 지연 취소(deferred cancellation) : 목적 스레드가 주기적으로 자신이 강제 종료해야 하는 지를 검사한다.

스레드 취소를 어렵게 만드는 것은 취소 스레드들에게 할당된 자원 문제이다. 또한 스레드가 다른 스레드와 공유하는 자료구조를 갱신하는 도중에 취소 요청이 와도 문제가 된다. 운영체제가 취소된 스레드로부터 시스템 자원을 회수하지 못 할 수도 있다.

비동기식 취소와는 반대로 지연 취소는 자신이 안전하다고 판단되는 시점에서 취소 여부를 검사할 수 있다. Pthreads는 이러한 지점을 취소점(cancellation point)라고 부른다.

4.4.3 신호 처리(Signal handling)

신호는 UNIX에서 프로세스에게 어떤 사건이 일어났음을 알려주기 위해 사용된다. 신호는 알려줄 사건의 근원지나 이유에 따라 동기식 또는 비동기식으로 전달될 수 있다.

  • 신호는 특정 사건이 일어나야 생성된다.

  • 신호가 생성되면 프로세스에게 전달된다.

  • 신호가 전달되면 반드시 처리되어야 한다.

모든 신호는 둘 중 하나의 처리기에 의해 처리된다.

  1. 디폴트 신호 처리기

  2. 사용자 정의 신호 처리기

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

단일 스레드 프로그램에서 신호 처리는 간단하다. 신호는 항상 프로세스에게 전달된다. 그러나 프로세스가 여러 스레드를 가지고 있는 다중 스레드 프로그램에서의 신호 처리는 더욱 복잡하다. 다음과 같은 선택지가 있다.

  • 신호가 적용될 스레드에게 전달한다.

  • 모든 스레드에게 전달한다.

  • 몇몇 스레드들에게만 선택적으로 전달한다.

  • 특정 스레드가 모든 신호를 전달받도록 지정한다.

4.4.4 스레드 풀(Thread pools)

스레드 풀의 기본 아이디어는 프로세스를 시작할 때 아예 일정한 수의 스레드들을 미리 풀로 만들어두는 것이다. 이 스레드들은 평소에는 하는 일 없이 일감을 기다리게 된다. 그러다 하나의 요청이 들어오면 이 풀의 한 스레드에게 서비스 요청을 할당한다. 요청을 다 서비스해 주었다면 그 스레드는 다시 풀로 돌아가 다음 작업을 기다린다. 풀에 남아있는 스레드가 없으면 가용 스레드가 생길 때까지 기다려야 한다.

스레드 풀은 아래와 같은 장점을 가지게 된다.

  • 새 스레드를 만들기보다 기존 스레드로 서비스하는 것이 더 빠르다

  • 스레드 풀은 임의 시간에 존재할 스레드 개수에 제한을 둔다. 이런 제한은 많은 수의 스레드를 병렬 처리할 수 없는 시스템에 도움이 된다.

4.4.5 스레드별 데이터(Thread-Specific Data)

한 프로세스에 속한 스레드들은 그 프로세스의 데이터를 모두 공유한다. 이런 데이터 공유는 다중 스레드 프로그래밍의 큰 장점 중 하나다. 그러나 상황에 따라서는 각 스레드가 자기만 액세스할 수 있는 데이터를 가져야 할 필요가 있다. 그런 데이터를 스레드별 데이터라고 부른다.

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

스레드 라이브러리와 커널의 통신 문제를 고려해야한다. 이 통신은 다대다 및 두 수준 모델에서 반드시 해결해야 할 문제이다.

다대다 또는 두 수준 모델을 구현하는 많은 시스템들은 사용자와 커널 스레드 사이에 중간 자료 구조를 둔다. 이 자료 구조는 통상 경량 프로세스 또는 LWP라고 부른다.

image
경량 프로세스(LWP)

사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중의 하나는 스케줄러 액티베이션이라 알려진 방법이다. 커널은 응용에게 가상 처리기(LWP)의 집합을 제공하고 응용은 사용자 스레드를 가용한 가상 처리기로 스케줄 한다. 커널은 응용에게 특정 사건에 대해 알려줘야 한다. 이 프로시저를 업콜이라 부른다.

4.5 운영체제 사례(Operating-System Examples)

4.5.1 Windows XP 스레드

스레드의 일반적인 구성요소

  • 각 스레드를 유일하게 지목하는 스레드 ID

  • 처리기의 상태를 나타내는 레지스터 집합

  • 사용자 모드에서 사용할 사용자 스택, 커널 모드에서 사용할 커널 스택

  • 실행시간 라이브러리와 동직 링크 라이브러리(DLL) 등이 사용하는 개별 데이터 저장 영역

4.5.2 Linux 스레드(Linux ThreadS)

프로세스를 복제하는 기능을 가진 fork() 시스템 호출을 제공한다. Linux는 clone() 시스템 호출을 이용하여 스레드를 생성할 수 있지만 프로세스와 스레드를 구별하지 않는다.

4.6 요약

스레드는 프로세스 안의 제어 흐름이다. 다중 스레드 프로세스는 같은 주소 공간 안에 여러 개의 서로 다른 제어 흐름을 가지고 있다. 다중 스레딩의 장점은 사용자에 대한 향상된 응답성, 프로세스 안의 자원 공유, 경제성 및 더 효율적인 다중코어의 이용과 같은 규모 가변성 등이다.

사용자 수준 스레드는 커널의 개입이 필요로 하지 않기에 커널 수준 스레드보다 빠르게 생성되고 빠르게 관리된다. 사용자와 커널 스레드와 관련된 모델은 다대일 모델(여러 개의 사용자 - 하나의 커널), 일대일 모델(각각 사상) 다대다 모델(많은 사용자 - 그보다 적은 수 또는 같은 커널 스레드로 멀티플렉스)의 세 가지가 있다.

다중 스레드 프로그램은 프로그래머에게 시스템 호출의 의미, 스레드 취소, 신호 처리, 스레드별 데이터 등의 새로운 숙제를 안겨준다.

comments powered by Disqus