Kim Jinung
4. 스레드와 병행성 본문
스레드란 무엇인가? 프로세스와 스레드?
운영체제가 제공하는 프로세스 모델에서 프로세스는 스레드라는 단위로 작업을 수행한다. (이것은 CPU의 작업 단위이기도 하다.) 초기 컴퓨터 시스템은 싱글 스레드 프로세스만 존재했다. (스레드가 멀티로 구성된다고 해도 결국 병행 실행이라 의미가 없었다.) 폰 노이만 아키텍처에서 프로그램은 메모리에 적재되고 이를 CPU가 연산한다. 그러므로 싱글 코어 CPU 칩에서는 한 번에 하나의 스레드밖에 실행할 수 없다.(하나의 프로세스가 모든 CPU 리소스를 점유하게 되므로) 운영체제에서는 이를 극복하기 위해서 여러 프로세스를 번갈아가며 작업하는 멀티 프로그래밍 기법이 등장했다. 이것이 병행성이다(Parallel). 시간이 흘러 멀티 코어 CPU가 등장했다. 이는 CPU 연산이 필요한 작업을 동시에 여러 개 수행할 수 있게 됨을 의미한다. 이것이 병렬성이다(Concurrency). 멀티 코어 CPU의 등장으로 프로세스는 멀티 스레드를 활용하여 병렬 작업을 수행할 수 있게 되었다. 단적인 예로 싱글 스레드를 지원하는 프로세스는 특정 연산 중에 다른 버튼을 눌러도 반응하지 않는다. 이에 응답해줄 스레드가 없기 때문이다. 반면에 멀티 스레드를 지원하는 프로그램의 경우 작업 중에 다른 스레드가 유저 입력에 반응할 수 있다. 요악하자면 싱글 코어 칩 환경에서는 여러 프로세스를 동시에 실행하기 위한 멀티 프로그래밍 개념이, 멀티 코어 칩이 등장한 환경에서는 프로세스 내에서 멀티 코어를 활용하기 위한 멀티 스레드 개념이 등장하게 된 것이다. (쓰레드라는 용어 자체가 멀티 쓰레드 환경이 갖추어지고 나서 보편화 되었다고 한다.)
스레드의 구성
스레드는 프로세스의 코드, 데이터 섹션과 열려있는 파일 그리고 운영체제 자원들을 공유한다.
그리고 각 스레드는 다음과 같은 자신만의 자원을 가지고 있다.
- 스레드 ID
- Program Counter
- 레지스터 집합
- 스택
하나의 개별 작업을 수행하기 위한 영역은 별도로 할당 받고 그렇지 않은 공통 영역은 모두 프로세스 내에서 공유 받는 구조다.
프로세스와 멀티 스레드
멀티 스레드를 사용함으로써 얻게 되는 이점은 다음과 같다.
웹 서버가 클라이언트의 요청을 받을 때 싱글 스레드 프로세스는 하나의 응답을 완료하기 전까지 다른 유저의 액션에 응답하지 못한다. 스레드가 작업 중이므로 이를 완료해야만 다른 작업이 가능한 것이다. (이를 해결하기 위한 방법이 node.js 에서 사용하는 non-blocking I/O와 같은 비동기 처리인 것 같다.) 이에 대한 해결책으로 클라이언트의 요청이 발생했을 때 웹 서버의 프로세스는 자식 프로세스를 생성하고 이 자식 프로세스가 요청에 응답하는 방식이 있다. 이러한 형태는 스레드 개념이 보편화 되기 이전에 자주 사용하던 방식이다. 그런데 프로세스를 생성하는 작업은 시간 뿐만 아니라 많은 하드웨어 리소스를 필요로 한다. 즉 같은 작업을 하기 위한 프로세스를 계속해서 반복 생성하므로 불필요한 오버헤드가 발생한다. 멀티 스레드를 지원하는 웹 서버에서는 클라이언트의 요청을 받았을 때 해당 요청에 반응하기 위한 스레드를 생성한다. 데이터, 코드 파일 등과 같은 공유 영역을 공유 받아서 요청을 처리하고 요청을 마치면 리소스를 반납하게 되는 것이다.
- 응답성: 멀티 스레드 프로세스는 작업을 진행 중이더라도 유휴 스레드가 유저 액션에 응답 할 수 있다.
- 자원 공유: IPC를 구현하기 위해서 공유 메모리와 메시지 전달 기법을 필요로 하는데, 스레드는 속한 프로세스의 자원들과 메모리를 공유한다. 즉 같은 메모리 영역을 동시에 작업할 수 있으므로 별도의 기법이 필요하지 않다.
- 경제성: 프로세스는 생성 시 모든 영역을 메모리에 적재해야 하므로 자원을 공유하는 스레드에 비해 생성을 위한 시간과 메모리 리소스를 많이 필요로 한다.
- 확장성: 멀티 CPU 시스템에서 스레드를 병렬로 실행 할 수 있다.
멀티 스레드를 구현하기 위한 도전과제
멀티 스레드 프로세스는 다음과 같은 추가적인 조건이 수반된다.
*Linux의 경우 프로세스와 스레드를 별도로 구분하지 않고 제어 흐름을 Task로 칭한다.
- 태스크 인식: 작업 단위를 독립적인 단위로 나누어야 한다.
- 균형: 병렬로 실행되는 작업들이 균등하게 실행될 수 있는가. (별도의 작업없이 자원만 점유하는 스레드가 있는지)
- 데이터 분리: 애플리케이션이 독립된 태스크로 나누어지는 것처럼 태스크가 접근하고 조작하는 데이터도 별도 코어로 분리되어야 한다.
- 데이터 종속성: 각 태스크가 접근하는 데이터가 둘 이상의 태스크에 종속성이 있는가.
- 시험 및 디버깅: 멀티 스레드 프로세스는 단일 스레드 프로세스와 다르게 다양한 실행 결로가 존재하므로 디버깅이 더 복잡해진다.
Thread Library
스레드 라이브러리는 스레드를 생성하고 관리하기 위한 API를 제공하는데 크게 두 가지 방법이 있다.
1. 유저가 직접 구현한다.
2. 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현한다. 즉 시스템콜로 지원한다.
POSIX Pthread, Windows, Java 세 종류 라이브러리를 주로 사용한다. (2번 케이스에 해당)
암묵적 스레딩 (Implicit threading)
멀티 스레드 프로세스를 위한 추가 작업을, 유저가 아닌 컴파일러와 런타임 라이브러리가 대신 해주는 것을 암묵적 스레딩이라고 한다. 암묵적 스레딩에는 다음과 같은 방법들이 존재한다.
스레드 풀(Thread pool)
프로세스를 다시 생성하는 것보다 스레드를 생성하는 작업이 비교적 더 작은 자원을 소모하는 것은 맞지만, 이또한 계속해서 동일한 생성과 제거 작업을 반복해야한다. 즉 지속해서 스레드를 생성하는 작업과 스레드를 몇 개까지 생성할 것인가에 대한 한계를 정해야한다. 이러한 방법을 해결하는 방법 중 하나가 스레드 풀이다. 프로세스를 시작할 때 설정 해둔 수 만큼의 스레드를 미리 풀로 만들어 두어서 CPU 및 메모리 공간의 사용량을 제한한다. (DB의 connection pool 도 이와 같은 맥락인듯)
Fork-Join(명시적 스레드)
부모 스레드가 하나 이상의 자식을 fork하고 그 결과를 기다렸다가 Join하는 동기식 모델이다. 이 모델은 종종 명시적 스레드 생성이라고 특징지어 지지만 암묵적 스레딩에서도 사용된다.
이외에도 OpenMP, GCD 등이 존재
스레드 취소 방법
1. 비동기식 취소(asynchronous cancellation): 즉시 스레드 종료
2. 지연 취소(deferred cancellation): 스레드가 자신이 강제 종료 되어야 할지를 점검한다. 스레드 자신이 안전하다고 판단되는 시점에서 종료 가능
보통은 지연취소를 많이 사용한다. 비동기식 취소의 경우 운영체제가 자원을 제대로 회수하지 못하는 문제가 발생할 수 있다.
Low Weight Process(LWP), 스케줄러 액티베이션
유저 스레드와 커널 스레드 사이에 위치하는 자료구조로, 동시성을 제어하기 위한 일종의 인터페이스 역할을 한다. 스레드 라이브러리와 커널의 통신을 조정하기 위한 자료구조다.
ex) 커널 스레드가 3개인데 유저 스레드가 4개인 경우 4번째 유저 스레드는 LWP 락이 풀리기 전 까지 대기해야한다.
'Computer Science > Operating System' 카테고리의 다른 글
6. 프로세스 동기화 그리고 데드락 (0) | 2023.01.06 |
---|---|
5. CPU 스케줄링 (0) | 2023.01.05 |
3. 프로세스 (0) | 2022.12.30 |
2. 운영체제 구조 (0) | 2022.12.29 |
1. 운영체제 서론 (0) | 2022.12.29 |