본 자료 정리는 'Operating System Concepts'(Tenth Edition) - Abraham Silberschatz 원서에 출처합니다.
Copyright © 2020 John Wiley & Sons, Inc.
A thread is a basic unit of CPU utilization.
(스레드는 CPU 이용의 기본 단위이다.)
스레드 구성요소
스레드는 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일이나 신호와 같은 운영체제 자원들을 공유한다.
과거 프로세스는 하나의 제어 스레드만을 가지고 있었지만, 현재는 다수의 제어 스레드(multithreaded)를 가지며 프로세스는 동시에 하나 이상의 작업을 수행할 수 있다.
하나의 응용 프로그램이 여러 개의 비슷한 작업을 처리해야 할 상황에서, 만약 웹 서버가 단일 스레드 프로세스로 작동한다면, 한 번에 하나의 클라이언트만 처리할 수 있게 돼서 매우 긴 시간이 걸리게 된다.
이를 위한 방법으로는, 서버에게 서비스 요청이 들어오면, 프로세스는 그 요청을 수행할 별도 프로세스를 생성하는 것이다.
하지만 프로세스 생성 작업은 매우 많은 시간을 소비하고 많은 자원을 필요로 한다.
따라서 대부분은 프로세스 안에 여러 스레드를 만들어 나가는 것이 더 효율적이다.
(Process creation is heavy-weight while thread creation is light-weight)
응답성 (responsiveness) : 프로세스의 일부가 차단되거나 긴 작업을 수행하는 경우 프로그램이 계속 실행하는 것을 허용함으로써, 사용자에 대한 응답성을 증가시킨다.
자원 공유 (resource sharing) : 스레드는 프로세스의 리소스를 공유하므로 공유 메모리 또는 메시지 전달보다 쉽다.
경제성 (economy) : 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥 교환하는 것이 프로세스에서 문맥 교환하는 것 보다더욱 빠르고 효율적이다.
규모 적응성 (scalability) : 다중 프로세서 구조에서 각각의 스레드는 다른 프로세서에서 병렬로 수행될 수 있기 때문에 규모 적응성에서 이점을 갖는다.
다중 스레드 프로그래밍은 multicore 또는 multiprocessor 시스템과 향상된 동시성을 보다 효율적으로 사용하기 위한 메커니즘을 제공한다.
Concurrency - 하나 이상의 작업이 모두 다 조금씩 진행 (어디서든 가능)
Parallel - 완전히 동시에 실행 (멀티코어에서 가능)
프로그래머가 멀티코어 또는 멀티프로세서 시스템을 구현하기 위해서는 다음과 같은 도전과제를 극복해야 한다.
- 태스크 인식 (dividing activities) : 단위로 분할
- 균형 (balance) : 작업량을 균등하게 배분
- 데이터 분리 (data splitting) : 데이터 나누기
- 데이터 종속성 확인 (data dependency)
- 태스팅과 디버깅 (testing and debugging)
직렬(비병렬) 및 병렬 구성 요소가 모두 있는 애플리케이션에 추가 코어를 추가하여 성능 향상을 식별한다.
(e.g. 응용 프로그램이 75% 병렬, 25% 직렬인 경우 2코어에서 4코어로 이동하면 속도가 1.6배에서 2.28배로 빨라진다.)
N이 무한대에 접근함에 따라 속도 향상은 1/S에 접근한다.
(e.g. 50% 가 serial일 때, N을 무한대로 늘리더라도 최대 속도 향상은 1/0.5 = 2배 이다.)
응용 프로그램의 직렬 부분은 추가 코어를 추가하여 얻은 성능에 불균형적인 영향을 미친다.
User threads : 커널 위에서 지원되고 user-level 스레드 라이브러리에 의해 커널 지원 없이 관리된다.
Kernel threads : 커널에서 직접 지원 및 관리한다.
Three multithreading models
단일 kernel 스레드에 많은 user-level 스레드가 매핑된다.
(-) 하나의 스레드가 차단되면 모두 차단된다.
(-) 한 번에 하나의 스레드만 커널에 있을 수 있으므로 다중 스레드는 multicore 시스템에서 병렬로 실행되지 않을 수 있다.
각 user-level 스레드가 kernel 스레드에 매핑된다.
사용자 수준 스레드를 생성하면 커널 스레드가 생성된다.
(+) 스레드가 차단 시스템 호출을 할 때 다른 스레드가 실행되도록 허용함으로써 Many-to-One 보다 더 많은 동시성을 가진다.
(+) 다중 스레드가 다중 프로세서에서 병렬로 실행될 수 있다.
(-) 오버헤드로 인해 때때로 프로세스당 스레드 수가 제한된다.
다수의 user-level 스레드를 더 작거나 같은 수의 커널 스레드로 다중화한다.
(+) OS가 충분한 수의 커널 스레드를 생성할 수 있도록 한다.
(+) 다중 스레드가 다중 프로세서에서 병렬로 실행될 수 있다.
(+) 스레드가 차단 시스템 호출을 만들 때 다른 스레드가 실행되도록 허용한다.
(-) 구현하기 까다롭다.
num(user threads) >= num(kernel threads)
Many-to-Many 모델의 변형이다.
많은 사용자 수준 스레드를 더 작거나 같은 수의 커널 스레드에 다중화하지만 사용자 수준 스레드를 커널 스레드에 바인딩할 수도 있다.
num(user threads) >= num(kernel threads) && One-to-One model 가능
스레드 라이브러리(thread library)는 프로그래머에게 스레드 생성 및 관리를 위한 API를 제공한다.
구현하는 두 가지 주요 방법
주된 스레드 라이브러리
Asynchronous vs. synchronous threading
Example:
-> 전역변수
-> P.T (부모 스레드)
-> C.T (자식 스레드)
-> P.T (부모 스레드)
-> C.T (자식 스레드)
다중 코어 처리의 성장에 따라 수백, 수천 개의 스레드를 가진 Application이 등장하게 되었다.
스레딩의 생성과 관리 책임을 프로그래머로부터 컴파일러와 실행시간 라이브러리에게 넘겨주는 암묵적 스레딩(Implicit threading)을 사용하면 멀티 스레딩을 효율적으로 활용할 수 있다.
이 방법은 개발자는 병렬 작업만 실행하면 되고 라이브러리가 세부 사항을 결정하는 것이다.
이는 여러가지 방법이 있다.
웹 서버는 요청을 받을 때마다 새로운 스레드를 만들어 준다. 하지만 이는 여러 문제가 있다.
하나의 웹 서버에 수 천개의 스레드를 생성하고 종료하는데에는 오버헤드가 발생한다. 또한 메모리 고갈 문제가 발생한다.
이에 대한 해결 방안이 작업을 기다리는 풀에 여러 스레드를 미리 만들어 두는 것이다.
스레드 풀의 장점
(+) 일반적으로 새 스레드를 만드는 것보다 기존 스레드로 요청을 처리하는 것이 약간 빠르다.
(+) 스레드 풀은 임의 시각에 존재할 스레드 개수에 제한을 둔다. 이러한 제한은 많은 수의 스레드를 병렬 처리할 수 없는 시스템에 도움이 된다.
(+) 태스크를 생성하는 방법을 태스크로부터 분리하면 태스크의 실행을 유연하게 할 수 있다.
부모 스레드는 자식 스레드를 생성(fork)한 다음 대기 및 조인(join)
그러나 스레드는 포크 단계에서 직접 구성되지 않고 병렬 작업이 지정된다.
라이브러리는 생성된 스레드 수를 관리하고 스레드에 작업을 할당한다.
스레드 풀의 동기 버전이다.
재귀 분할 정복 알고리즘을 위해 설계된 Fork-join 라이브러리
(e.g. Quicksort, Mergesort)
하나의 큰 문제를 나누어 병렬 실행한다. 작업을 더 이상 나눌 수 없게되면 fork를 시켰던 부모 스레드로 조인해나가며 전체 문제를 해결한다.
C, C++, FORTRAN용 컴파일러 지시문 및 API 세트이다.
공유 메모리 환경에서 병렬 프로그래밍을 위한 지원을 제공한다.
병렬 영역(parallel regions)을 병렬로 실행할 수 있는 코드 블록으로 식별한다.
macOS 및 iOS 운영 체제용 Apple 기술이다.
개발자가 병렬로 실행할 코드(작업) 섹션을 식별할 수 있도록 하는 런타임 라이브러리, API, 언어 확장의 조합이다.
dispatch queue에 작업을 배치한다.
Task expression
fork()는 호출 스레드만 복제하는가 아니면 모든 스레드를 복제하는가?
- 일부 UNIX 시스템에는 두 가지 버전이 있다.
exec()는 일반적으로 정상적으로 작동한다 .
- 모든 스레드를 포함하여 실행 중인 프로세스를 교체한다.
신호 (signal)는 UNIX 시스템에서 특정 이벤트가 발생했음을 프로세스에 알리기 위해 사용된다.
신호처리기 (signal handler)는 신호를 처리하는 데 사용된다.
1) 신호는 특정 이벤트에 의해 생성된다.
2) 신호가 프로세스에 전달된다.
3) 신호는 두 가지 신호 처리기 중 하나에 의해 처리된다.
- (default / user-defined)
모든 신호에는 신호를 처리할 때 커널이 실행하는 기본 핸들러 (default handler)가 있다.
다중 스레드의 경우 신호를 어디로 전달해야 하는가?
신호가 적용되는 스레드에 신호 전달
프로세스의 모든 스레드에 신호 전달
(e.g. 프로그램이 예기치 않게 종료된 경우)
프로세스의 특정 스레드에 신호 전달
프로세스에 대한 모든 신호를 수신할 특정 스레드 할당
스레드가 완료되기 전에 종료하는 것이다. (강제종료)
취소되는 스레드는 타켓 스레드이다.
두 가지 일반적인 접근 방식
pthread_cancel() 호출은 취소만 요청하지만 실제 취소는 스레드 상태에 따라 다르다.
스레드에 취소가 비활성화(disabled)되어 있으면 스레드가 활성화(enabled)할 때까지 취소가 보류 상태로 유지된다.
default type은 deferred이다.
스레드 로컬 저장소(Thread-Local Storage)를 사용하면 각 스레드가 자체 데이터 복사본을 가질 수 있다.
스레드 생성 프로세스를 제어할 수 없을 때 유용하다. (e.g. 스레드 풀을 사용하는 경우)
지역 변수와 다르다
static 데이터와 유사하다. 그러나 TLS는 각 스레드마다 고유하다.
M:M 및 Two-level 모델 모두 응용 프로그램에 할당된 적절한 수의 커널 스레드를 유지하기 위해 통신이 필요하다.
일반적으로 사용자 스레드와 커널 스레드 간에 중간 데이터 구조를 사용한다.
스케줄러 활성화(scheduler activation)는 *upcall을 제공한다.
-*upcall: 커널에서 스레드 라이브러리의 upcall handler로의 통신 메커니즘
이 통신을 통해 애플리케이션은 올바른 수의 커널 스레드를 유지할 수 있다.
수정해야될 사항이 있거나 잘못 번역된 문장이 있을경우 댓글로 알려주세요 :)