본문 바로가기

CS 리마인드/운영체제

[운영체제] 4. 스레드

 본 내용들은 전공수업으로 배운 내용들을 스스로 리마인드하기 위한 목적으로 작성되었으며, 누군가에게 지식을 전달하기 위한 목적으로 작성한 것이 아닙니다. 대부분의 내용은 제가 수강했던 전공수업 및 Operating System Concepts, 10th Edition에 근거하고 있습니다.

 

1. 개요

 현대의 대부분의 응용 프로그램들은 멀티 스레드 기반으로 동작한다. 응용 프로그램들이 보통 다수의 태스크를 동시에 처리해야 하기 때문인데, 당장 워드만 사용해도 UI를 표시하면서 지속적으로 맞춤법을 검사하며, 동시에 저장소의 데이터를 읽거나 쓰는 것도 가능하다. 

 프로세스는 저번 챕터에서 본 것처럼 생산 및 관리의 오버헤드가 크기 때문에, 오버헤드를 줄이고 보다 효율적으로 다수의 태스크를 처리하기 위해 등장한 개념이 바로 스레드다. 한 프로세스 내에서 생성되고 관리되기 때문에, 커널 스레드를 사용하는 것이 아닌 이상 커널 모드로 전환하는 Context Switch가 필요하지 않고, 프로세스 내의 Data, Text, Heap 영역을 공유해서 쓸 수 있다. 물론 스레드별로 Program Counter나 Stack 등은 따로 관리해야 하므로, 스레드 간의 전환(스위칭) 비용이 없는 것은 아니지만 상기한 이유들 때문에 비용이 훨씬 적게 든다.

 

 그렇기 때문에 스레드를 사용한 멀티 태스킹은 다음과 같은 이점을 가진다.

- Responsiveness(반응성): 프로세스를 부분적으로 block 하면서, UI처럼 반응성이 중요한 부분을 지속적으로 실행할 수 있다.

- Resource Sharing(리소스 공유): 프로세스 내의 공유 영역을 통해 데이터 공유가 쉽다. 이는 저번 챕터에서 봤던 shared memory, message passing 방식의 IPC보다 훨씬 비용이 적다.

- Economy(경제성): 프로세스의 생성 및 관리보다 스레드의 생성 및 관리가 훨씬 쉬우며, 스위칭 오버헤드 또한 작다.

- Scalability(확장성): 멀티코어 아키텍처를 효율적으로 사용할 수 있다.

 

2. 멀티코어 프로그래밍

개념

멀티코어: 하나의 CPU 칩 안에 여러 코어가 들어있는 것.

멀티프로세서: 한 시스템 내에 CPU 칩이 여러 개인 것.

멀티스레딩: 한 프로세스 내에서 여러 개의 스레드를 사용하는 것.

동시 멀티스레딩(SMT: Simultaneous MT): 코어 내의 물리적 스레드와 논리적 스레드를 활용해, 복수 개의 작업을 병렬적으로 실행하는 것. ex) 인텔의 하이퍼 스레딩

슈퍼스칼라: CPU가 한 사이클 내에 여러 명령어를 동시에 실행하는 아키텍처.

Parallelism(병렬성): 시스템이 실제로 동시에 둘 이상의 작업 수행을 지원

Concurrency(동시성): 둘 이상의 작업 수행을 지원(OS가 스케줄러를 통해 제공하는 동시 실행이 바로 이 개념이다)

 

- 하지만 멀티코어 프로그래밍은, 태스크 정의를 엄밀하게 하고 그 밸런스를 생각해야 하며, 데이터 분할과 의존성 문제 등 섬세하게 고려할 게 많기 때문에 현실에서는 0번 CPU 코어가 대부분의 일을 하고 있을 가능성이 높다..

- 암달의 법칙에 따르면, 결국 애플리케이션 내의 serial component들 때문에 코어 추가로 인한 성능 향상 이득은 효율 상한이 있음을 시사한다.

 

3. 멀티스레딩 모델

유저 스레드: 

- 커널 개입 없이 유저 영역 스레드 라이브러리 활용

- 스케줄링 결정이나 동기화를 위해 커널을 호출하지 않아 오버헤드가 작다

- 시스템 전반의 스케줄링 우선순위가 지원되지 않는다

- 시스템 콜 시 프로세스 내 모든 스레드가 block 된다

 

커널 스레드:

- 커널이 관리하는 스레드, OS 스케줄러의 최소 단위(TCB)

- 하나의 프로세스는 적어도 하나의 커널 스레드를 가진다

- 커널 영역에서 스케줄링과 같은 스레드 연산을 수행하며, 이는 커널에 종속적이다

- 커널이 직접 프로세서에 스케줄링 및 동기화하며 안정적이고 기능도 많다

- 유저 스레드와의 전환이 빈번하면 성능 저하가 발생하기 때문에, 구현 비용이 크다

 

따라서 멀티스레딩을 구현할 때는 다음과 같은 모델들을 생각할 수 있다.

 

Many-to-One(다대일): 

- 복수 개의 유저 스레드가 커널 스레드에 매핑

- 하나의 스레드가 block 되면 다른 스레드들도 모두 block 된다(OS 입장에선 커널 스레드 하나인거고, 시스템 콜 등으로 block이 발생하면 더 이상 해당 커널 스레드 쪽에 CPU 리소스가 부여되지 않으니까)

- 프로그래머는 원하는 만큼 유저 스레드를 만들기 쉽다

- 이 모델로 구현한 멀티스레딩은 멀티코어 시스템에 의한 병렬 실행이 불가능하다

- 그래서 이런 모델을 채택하는 시스템은 적다(Solaris Green Threads, GNU Portable Threads)

 

One-to-One(일대일):

- 각각의 유저 스레드에 커널 스레드가 하나씩 매핑

- 유저 스레드를 생성하면 커널 스레드도 생성된다

- Many-to-One 모델보다 우수한 동시성을 제공할 수 있다

- 대신 오버헤드가 크기 때문에, 프로세스 당 활용 가능한 스레드 수에 제한이 생기기도 한다

- Windows, Linux

 

Many-to-Many(다대다):

- 다수의 유저 스레드에 다수의 커널 스레드가 매핑

- OS가 충분한 양의 커널 스레드를 생성할 수 있다

- Windows의 ThreadFiber 패키지 

- 일반적이지는 않다

 

Two-level: 

- 기본적으로 Many-to-One 모델처럼 매핑되지만, 유저에 의해 커널 스레드를 추가할 수 있어 필요에 따라 One-to-One이나 Many-to-Many 모델처럼 동작하기도 한다.

 

4. 기타

스레드 라이브러리: Pthread(POSIX 표준 API, UNIX 계열 OS에서 일반적으로 사용됨), Java Thread(혹은 Executor 프레임워크를 이용한 암묵적 스레드) 등

암묵적 스레딩: 스레드의 수가 증가하면 그 생성 및 관리도 어려워진다. 이를 컴파일러나 런타임 라이브러리 차원에서 담당하게 한 것. ex) Thread Pools, Fork-Join, OpenMP, Grand Central Dispatch, Intel Threading Building Blocks 등 

스레딩 이슈들:

- fork() 및 exec() 정의 문제(멀티스레드 프로세스에서 한 스레드가 fork()를 호출하면 새로운 프로세스는 모든 스레드를 복제해야 하는가, exec() 실행 시 모든 스레드를 포함하여 메모리 영역을 대체할 것인가 등)

- 시그널 핸들링(특정 프로세스에 전달된 다양한 인터럽트나 트랩 상황에서, 이를 각 스레드에 대해 어떻게 처리할 것인지 문제. 시그널 핸들러 구현으로 해결)

- 스레드 취소 문제(스레드 취소를 요청했을 때, 해당 스레드가 즉시 취소되면 shared resource problem이 발생할 수 있다. 따라서 스레드 상태에 따라 지연 취소하는 것이 일반적)

- Thread-Local Storage(TLS)(스레드 풀 사용 시에 유용하며, 데이터를 스레드 내의 고유한 전역/정적 변수처럼 사용한다)

- 스케줄러 활성화(Many-to-Many 모델이나 Two-level 모델은 애플리케이션에 할당된 커널 스레드를 적정 개수로 유지할 필요가 있다. 보통 유저 스레드와 커널 스레드 사이의 중재자 자료 구조로 lightweight process(LWP)를 사용하게 된다. 커널은 애플리케이션에 LWP set을 제공하고, 애플리케이션은 유저 스레드를 LWP로 예약한다. 커널이 특정 이벤트에 대해 애플리케이션에 알리는 프로세스가 upcall)

 

'CS 리마인드 > 운영체제' 카테고리의 다른 글

[운영체제] 5. CPU 스케줄링  (0) 2024.12.09
[운영체제] 3. 프로세스  (3) 2024.12.01
[운영체제] 2. OS 서비스  (1) 2024.03.29
[운영체제] 1. 서론  (1) 2024.03.22