스레드
개요
스레드는 CPU 이용의 기본단위이다.
스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택 으로 구성된다.
스레드는 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션 그리고 열린 파일이나 신호와 같은 OS 자원을 공유한다.
단일 및 다중 스레드 프로세스 |
어떤 프로세스가 다수의 제어 스레드를 가진다면, 해당 프로세스는 동시에 하나 이상의 작업들을 수행할 수 있다.
동기(Motivation)
다수의 OS 커널은 다중화 되어 있다. 커널 안에서 다수의 스레드가 동작하고 각 스레드는 장치 또는 인터럽트 처리 등의 특정 작업을 수행한다.
다중 스레드 서버 구조 |
장점
다중 스레드 프로그래밍이 가지는 이점은 아래와 같다.
- 응답성(Responsiveness)
- 대화형 응용 프로그램을 다중 스레딩하면 응용 프로그램의 일부분이 봉쇄되거나, 응용 프로그램이 장시간 작업을 수행하더라도 프로그램의 수행이 계속 되는 것을 허용하여 사용자에 대한 응답성을 증가시킨다.
- 자원 공유(Resource Sharing)
- 스레드는 자동적으로 그들이 속한 프로세스의 자원들과 메모리를 공유한다. 즉 코드와 데이터 공유를 통해 같은 주소 공산내에 여러개의 다른 작업을 하는 스레드를 가진수 있다.
- 경제성(Economy)
- 프로세스 생성을 위해 메모리와 자원을 할당하는 것은 비용이 많이 든다.
- 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥 교환하는 것이 보다 더 경제적이다.
- 규모 적응성(Scalability)
- 다중 처리기 구조에서는 각각의 스레드가 다른 처리기에서 병렬로 수행될 수 있으므로 더욱 성능이 좋아진다.
다중코어 프로그래밍
병렬 실행(Parallel Excution) 과 병행 실행(Concurrent Excution)
- 병렬 실행
- 하나 이상의 태스크를 동시에 수행할 수 있는 시스템에 대하여 병렬적 이라고 한다.
- 병행 실행
- 모든 태스크가 진행하도록 하여, 하나 이상의 태스크를 지원한다. 따라서 병렬 실행 없이 병행실행이 가능하게끔 한다.
단일코어 시스템에서의 병행 실행 |
다중코어 시스템에서의 병렬 실행 |
프로그래밍 챌린지
다중코어 시스템을 위해 프로그래밍하기 위해, 아래와 같은 5개의 극복해야할 도전이 있다.
- 태스크 인식(Identifying Tasks)
- 개발하고자 하는 응용 프로그램을 분석하여 독립된 병행가능 태스크로 나눌수 있는 영역을 찾아야 한다.
- 균형(Balance)
- 찾아진 병렬실행 가능 부분들이 전체 작업에 균등한 기여도를 가지도록 태스크를 나누어야 한다.
- 데이터 분리(Data Spliting)
- 태스크가 접근하고 조작하는 데이터 또한 개별 작업에서 사용될수 있도록 나누어져야 한다.
- 데이터 의존성(Data Dependency)
- 태스크가 접근하는 데이터는 둘 이상의 태스크 사이에 종속성 및 의존성이 없는지 확인하여야 한다.
- 한 태스크가 다른 태스크로부터 오는 데이터에 종속적이거나 의존성이 있을때, 이를 수용할 수 있도록 태스트의 수행을 동기화하여 데이터 종속에 오는 문제를 회피한다.
- 테스트 및 디버깅(Testing and Debugging)
- 단일 스레드 프로그램의 테스트와 디버깅보다 난이도 있기때문에, 주의하여야 한다.
병렬 실행의 유형
일반적으로 데이터 병렬 실행 과 태스크 병렬 실행 두가지가 있다.
- 데이터 병렬 실행
- 동일한 데이터의 부분집합을 다수의 계산 코어에 분배한 뒤, 각 코어에서 동일한 연산을 실행하는데 초첨을 맞춘다.
- 태스크 병렬 실행
- 각 스테드는 고유의 연산을 수행한다.
- 각기 다른 스레드는 동일한 데이터에 대해연산을 실행할 수 있고, 또는 서로 다른 데이터에 연산을 실행할 수 있다.
다중 스레드 모델
스레드를 위한 자원은, 사용자 스레드(User Thread) 를 위해서는 사용자 수준에서, 또는 커널 스레드(Kernel Thread) 를 위해서는 커널 수준에서 제공된다.
사용자 스레드는 커널 위에서 지원되며 커널의 지원 없이 관리된다. 반면 커널 스레드는 OS에 의해 직접 지원되고 관리된다.
다대일 모델(Many-to-One Model)
많은 사용자 스레드를 하나의 커널 스레드로 매칭한 모델
스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다.
한 스레드가 블럭형 시스템콜을 호출 할 경우, 전체 프로세스가 블럭된다.
한번에 하나의 스레드만이 커널에 접근할 수 있기 때문에, 다중 스레드가 다중코어 시스템에서 병렬로 실행될 수 없다.
현대의 시스템에서는 거의 사용되지 않는다.
다대일 스레드 모델 |
일대일 모델(One-to-One Model)
하나 사용자 스레드를 각각 하나의 커널 스레드로 매칭한 모델
하나의 스레드가 블럭형 시스템콜을 호출 할 경우에도 다른 스레드가 실행될 수 있기 때문에 다대일 모델보다 더 많은 병렬성을 제공한다.
사용자 스레드를 생성할때, 그에 따른 커널 스레드를 생성해야 하므로, 오버헤드가 발생하여 성능저하가 일어날수 있다.
이 모델의 대부분의 구현은 시스템에 의해 지원되는 스레드의 수를 제한한다.
Windows 계열과, Linux가 일대일 모델을 구현하고 있다.
일대일 스레드 모델 |
다대다 모델(Many-to-Many Model)
여러개의 사용자 스레드를 그보다 작은 수, 혹은 같은 수의 커널 스레드로 매칭한 모델
한번에 하나의 스레드만이 커널에의해 스케쥴링 되기 때문에 진정한 병렬 실행을 이룰수 없다.
개발자는 일대일 모델과 달리 다대다 모델의 시스템에서는 필요한 만큼 사용자 스레드를 생성할 수 있다.
어떤 스레드가 블럭형 시스템콜을 호출했을때, 커널이 다른 스레드의 수행을 스케쥴링할 수 있다.
다대다 스레드 모델 |
두 수준 모델(Two-level Model)
다대다 모델의 변형으로써, 많은 사용자 스레스를 적거나 같은수의 커널 스레드와 매칭하지만, 또한 한 사용자 스레드가 하나의 커널 스레드에서만 매칭되는것을 허용
두 수준 모델 |
암묵적 스레딩
암묵적 스레딩(Implicit Threading) 이란, 스레딩 생성과 관리 책임을 컴파일러와 실행시간 라이브러리에게 넘겨주는 방식이다.
스레드 이슈
Fork() 및 Exec() 시스템 호출
다중 스레드 프로그램에서는 fork()와 exec()의 의미가 달라질 수 있다.
만일 하나의 프로그램의 스레드가 fork()를 호출하면, 해당 자식 프로세스는 부모 프로세스의 모든 스레드를 복제하여 가질 수 있고, fork()를 호출한 스레드만 복제하여 가질 수 있다.
신호 처리(Signal Handling)
신호 는 UNIX에서 프로세스에게 어떤 사건이 일어났음을 알려주기 위해 사용된다.
신호는 알려줄 사건의 근원지나 이유에 따라 동기식 과 비동기식 으로 전달될 수 있다.
모든 신호는 다음과 같은 형태로 전달된다.
- 신호는 특정 사건이 일어나야 생성된다.
- 생성된 신호가 프로세스에게 전달된다.
- 신호가 전달되면 반드시 처리되어야 한다.
동기식 신호의 예로는 불법적인 메모리 접근, 0으로 나누기 등이 있다. 동기식 신호는 신호를 발생시킨 프로세스에게 전달된다.
신호가 발생중인 프로세스 외부로부터 발생되면, 그 프로세스는 신호를 비동기식으로 전달 받는다. 비동기식 신호의 예로는 “CRTL + C”와 같은 특수한 키를 눌러 프로세스를 강제 종료 하거나 타이머가 만료되는 경우 이다.
모든 신호는 둘 중 하나의 처리기에 의해 처리된다.
- 디폴트 신호 처리기(Default Signal Handler)
- 사용자 정의 신호 처리기(User Design Signal Handler)
모든 신호마다 커널이 실행시키는 디폴트 신호 처리기가 있다. 이 디폴트 신호 처리기는 신호를 처리하기 위해 사용자 정의 신호 처리기로 대체될 수 있다.
다중 스레드 프로그램에서 신호를 전달한 스레드를 선택할 때, 다음과 같은 방법이 존재한다.
- 신호가 적용될 스레드에게 전달
- 모든 스레드에게 전달
- 몇몇 스레드에게만 선택적으로 전달
- 특정 스레드가 모든 신호를 전달 받도록 지정
취소(Cancellations)
스레드 취소는 스레드가 끝나기 전에 그것을 강제 종료 시키는 작업이다.
스레드의 취소는 두가지 방법으로 발생할 수 있다.
- 비동기식 취소(Asynchronous Cancellation)
- 한 스레드가 즉시 취소 시킬 스레드를 강제 종료 시킨다.
- 지연 취소(Deferred Cancellation)
- 취소할 스레드가 주기적으로 자신이 강제 종료 되어야 할지 점검한다.
스레드 취소를 어렵게 만드는 것은 취소 스레드들에게 할당된 자원 문제이다. 또한 스레드가 다른 스레드와 공유하는 자료구조를 갱신하는 도중에 취소 요청이 와도 문제가 된다.
스레드 로컬 저장소(Thread Local Storage)
한 프로세스에 속한 스레드들은 그 프로세스의 데이터를 모두 공유한다. 그러나 스레드는 때로 자신만 액세스 할 수 있는 데이터를 가져야 할 필요가 있다. 이러한 데이터를 스레드 로컬 저장소(Thread Local Storage) 이라고 한다.
TLS를 지역변수와 혼동하기 쉬운데, 지역변수는 하나의 함수가 호출되는 동한에만 보이는 반면, TLS는 전체 함수 호출에 걸쳐 보인다.
스케쥴러 액티베이션(Scheduler Activation)
다대다 또는 두 수준 모델을 가진 많은 시스템들은 사용자와 커널 스레드 사이에 중간 자료구조를 둔다. 해당 자료구조를 경량 프로세스(LWP) 라고 한다.
경량 프로세스(LWP) |
사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법중 하나는 스케쥴러 액티베이션(Scheduler Activation) 이다.
운영체제 사례
Windows 스레드
Windows에서는 일대일 스레드 모델을 사용한다.
스레드와 일반적인 구성요소는 다음과 같다.
- 스레드 ID
- 처리기의 상태를 나타내는 레지스터 집합
- 사용자 모드에서 실행될 때 필요한 사용자 스택, 커널 모드에서 실행될 때 필요한 커널 스택
- 실행 시간 라이브러리와 동적 링크 라이브러리(DLL) 등이 사용하는 개별 데이터 저장 영역
레지스터 집합, 스택, 개별 데이터 저장 영역들은 그 스레드의 문맥으로 불린다. 스레드를 위해서는 아래와 같은 자료구조를 가진다.
- ETHERAD - 실행 스레드 블록(Executive Thread Block)
- 스레드가 속한 프로세스를 가리키는 포인터와 그 스레드가 실행해야할 루틴의 주소를 가짐
- KTHREAD에 대한 포인터를 가짐
- KTHREAD - 커널 스레드 블록(Kernel Thread Block)
- 스레드의 스케쥴링 및 동기화 정보를 가짐
- 해당 스레드가 커널코드에서 실행될 때 사용되는 커널 스택과 TEBdp에 대한 포인터를 가짐
- TEB - 스레드 환경 블록(Thread Environment Block)
- 사용자 모드에서 실행될 때 접근되는 사용자 공간 자료구조
- 스레드 ID, 사용자 모드 스택, 스레드 로컬 저장소를 저장하기 위한 배열을 가짐
Windows 스레드의 자료구조 |
ETHREAD와 KTHREAD는 모두 커널안에 존재한다.
Linux 스레드
Linux는 프로세스를 복제하는 기능을 가진 fork() 시스템콜과 clone() 시스템콜을 이용하여 스레드를 생성할 수 있다.
Linux는 프로세스와 스레드를 구별하지 않는다. 사실 Linux는 프로그램 내의 제어 흐을을 나타내기 위해 프로세스 나 스레드 보다 태스크 라는 용어를 사용한다.
clone()이 호출될 때, 부모와 자식 태스크가 자료구조를 얼마나 공유 할 것인지 결정하는 플레그의 집합이 전달된다.
flag | Meaning |
---|---|
CLONE_FS | 파일 시스템 정보가 공유 |
CLONE_VM | 같은 메모리 공간이 공유 |
CLONE_SIGHAND | 신호 처리기가 공유 |
CLONE_FILES | 열린 파일의 집합이 공유 |