스레드 라이브러리 (Thread Libraries)
스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하는 API를 제공한다.
스레드 라이브러리의 구현은 2가지로 나뉘는데,
1. 모든 라이브러리를 커널의 지원 없이 사용자 공간에 제공한다. 이는 라이브러리의 함수를 호출하는 것이 곧 시스템 콜이 아닌 사용자 공간의 지역 함수를 호출한다는 것이라는 의미이다.
2. 운영체제에 의해 직접 지원되는 커널 수준의 라이브러리를 구현한다. 이는 라이브러리의 함수 호출이 곧 시스템 콜임을 의미한다.
Pthreads (POSIX Standard)
스레드의 생성과 동기화에 대한 API를 정의한 것이다. (Specification, not an Implementation)
C언어에서 pthread.h 헤더 파일을 include해 함수들을 사용할 수 있다.
Threading Issues
fork(), exec() 시스템 콜
이전의 프로세스 포스팅에서 fork() 시스템 콜은 완전한 복사본 또는 별도의 프로세스를 생성한다 했다.
그렇다면 멀티스레드 프로그램에서 fork()의 호출로 복제된 새 프로세스는 원본의 스레드도 모두 복사할까? 아니면 하나의 단일 스레드를 가진 새 프로세스가 생성될까? 일부 UNIX 시스템에서는 두 가지 경우를 선택할 수 있도록 fork() 시스템 콜을 지원한다.
exec() 시스템 콜은 어떨까? exec() 시스템 콜은 프로세스에서의 호출과 동일하게 작동한다. 만약 한 스레드가 exec()을 호출한다면, 그 프로세스는 모든 자신의 스레드를 포함하여 다른 것으로 교체된다.
취소 (Cancellation)
스레드의 취소는 스레드가 완료되기 전에 하던 일을 종료시키는 것을 말한다.
만약 여러 스레드가 동시에 데이터베이스를 검색하던 도중, 한 스레드가 검색 결과를 찾아냈다면 나머지 스레드는 하던 일을 중단할 것이다. (이는 구현에 따라 다르다)
취소의 대상이 되는 스레드를 타겟 스레드(Target Thread)라 한다. 타겟 스레드에 대한 취소는 다음과 같은 두 가지 시나리오로 일어난다.
비동기 취소 (Asynchronous Cancellation) : 한 스레드가 타겟 스레드를 즉시 종료시킨다.
지연 취소 (Deffered Cancellation) : 타겟 스레드는 앞 순서의 스레드가 종료되었는지 주기적으로 확인하며, 이를 통해 일련의 순서대로 종료시킬 수 있다.
시그널 핸들링 (Signal Handling)
시그널(Signal)은 UNIX 시스템에서 특정 이벤트가 발생했을 때 프로세스에게 이를 알리는 데 주로 사용되는 것으로, 시그널이 발생한 이유에 따라 동기 또는 비동기로 처리될 수 있다.
동기/비동기 여부에 관계없이, 시그널은 다음과 같은 패턴을 따른다.
- ① 특정 이벤트에 의해 시그널이 생성된다.
- ② 생성된 시그널이 프로세스로 전달된다.
- ③ 전달되었다면, 시그널은 Handling된다. (해당하는 액션을 취한다.)
동기 시그널의 예시로는 비정상적 메모리 접근, Division by 0 등으로 발생하는 시그널을 들 수 있다.
비동기 시그널은 실행 중인 프로세스 외부에서 이벤트가 발생했을 때 발생하며, Ctrl + C와 같은 강제종료 등이 발생했을 때 처리된다.
모든 시그널은 커널에 의해 실행되는 Default handler를 갖는다. 이를 사용자 정의 핸들러로 오버라이드하여 사용할 수 있다.
시그널은 언제나 프로세스에게 전달된다. 하지만 멀티스레드 프로그램에선 조금 복잡해진다.
여러 스레드를 가진 프로세스는 시그널을 어디로 전달해야 할까? 가능한 선택지는 다음과 같다.
- ① 시그널이 적용되는 스레드에게 전달
- ② 프로세스 내의 모든 스레드에게 전달
- ③ 프로세스 내의 특정 스레드에게 전달
- ④ 모든 스레드를 전담하는 특별한 스레드를 할당
이는 생성되는 시그널의 종류에 따라 달라진다.
동기 시그널은 그 시그널을 발생시킨 특정 스레드에게만 전달될 필요가 있지만, Ctrl + C와 같은 비동기 시그널은 반드시 프로세스 내의 모든 스레드에게 전달되어야 할 것이다. (강제종료이기 때문)
스레드 풀 (Thread Pool)
이전 포스팅에서 멀티스레딩을 웹 서버의 예시로 설명했다.
각각의 요청마다 스레드를 생성하는 것은 프로세스를 새로 만드는 것보다 더 좋은 방법이지만, 이러한 멀티스레드 서버는 잠재적인 문제가 존재한다.
요청을 수행하기 위해 생성된 스레드는 요청을 모두 수행하고 버려진다는 사실과, 시스템 내에서 동시적으로 돌아가는 스레드 갯수에 대한 상한선을 정하지 않았다는 것이다. 제한이 없는 스레드는 시스템의 자원을 모두 고갈시킬 것이다.
이러한 문제로 인해 스레드 풀을 사용하는 것이 해결책으로 등장하였다.
스레드 풀의 기본적인 아이디어는 프로세스가 시작할 때 미리 특정 수의 스레드를 만들어, 이를 pool 의 형태로 두는 것이다. 요청을 받으면, 이 pool로부터 사용 가능한 스레드 하나를 선택하여 요청을 수행하도록 한다.
만약 선택된 스레드가 요청을 모두 수행했다면 그 스레드는 다시 pool로 돌아가 자신에게 요청이 들어오는 것을 대기하게 된다.
이미 존재하는 스레드를 사용하는 것이 스레드의 생성을 기다리는 것보다 보통 더 빠르고, 스레드의 숫자에 대한 상한선이 지정되어 있기 때문에 자원을 더 효율적으로 사용할 수 있다.
스케줄러 활성 (Scheduler Activations)
다대다 모델로 구현되는 많은 시스템들은 유저 스레드와 커널 스레드 사이에 중간 자료구조 (Intermediate data structure)를 갖는다.
이 자료구조를 LWP(Light Weight Process)라 하며, 마치 유저 스레드를 스케줄링하는 가상 프로세서처럼 동작한다.
LWP는 커널 스레드에 붙어 있어, 커널 스레드가 block되면 LWP 또한 block되며 LWP에 붙어 있는 유저 스레드 또한 block된다.
※ 본 게시글은 『Operating System Concepts』 를 참고하여 작성되었습니다.
'운영체제' 카테고리의 다른 글
[OS/운영체제] CPU 스케줄링 (CPU Scheduling) - (2) (0) | 2020.11.09 |
---|---|
[OS/운영체제] CPU 스케줄링 (CPU Scheduling) - (1) (0) | 2020.11.09 |
[OS/운영체제] 스레드 (Thread) - (1) (0) | 2020.11.06 |
[OS/운영체제] 프로세스 (Process) - (3) (0) | 2020.11.04 |
[OS/운영체제] 프로세스(Process) - (2) (0) | 2020.11.04 |