01. 쓰레드란 무엇인가?
멀티 프로세스 기반 프로그램
둘 이상의 서로 다른 프로그램 실행을 위해서 둘 이상의 프로세스를 생성하는 것은 당연한 일이다. 그러나 하나의 프로그램이 두 가지 이상의 일을 동시에 처리하기 위해서도 둘 이상의 프로세스가 필요하다. 이는 여러 명의 접속자들에게 원활한 서비스를 제공하기 위해 클라이언트의 접속 요청이 있을 때마다 자식 프로세스를 생성하는 서버에서도 관찰할 수 있다. 이외에도 이렇게 둘 이상의 일을 동시에 처리해야 하는, 혹은 둘 이상의 실행 흐름을 필요로 하는 프로그램은 인터넷 게임에서도 찾아볼 수 있다.
멀티 프로세스 운영체제 기반 프로그램의 문제점과 새로운 제안
두 가지 이상의 일을 동시에 처리하기 위해, 혹은 둘 이상의 실행 흐름이 필요해서 추가적으로 프로세스를 생성하는 작업은 빈번한 컨텍스트 스위칭(Context Switching)으로 이어지기 때문에 성능 저하에 영향을 미친다. 이를 해결하기 위해, 컨텍스트 스위칭 과정에서 저장하고 복원하는 컨텍스트 정보의 개수를 줄이는 방법을 생각한다.
컨텍스트 스위칭이 필요한 가장 근본적인 이유는 프로세스들이 서로 완전히 독립적이기 때문이다. 따라서, A 프로세스와 B 프로세스가 완전히 독립되지 않고 50% 정도를 공유한다고 가정하면, 컨텍스트 스위칭 과정에서 저장 및 복원하는 정보가 반으로 줄게 된다.
이러한 생각을 기반으로 쓰레드라는 것이 탄생한다.
해결책, 쓰레드
앞서 언급한 쓰레드의 내용을 프로세스와 비교해서 정리하면 다음과 같다.
- 쓰레드는 하나의 프로그램 내에서 여러 개의 실행 흐름을 두기 위한 모델이다.
- 쓰레드는 프로세스처럼 완벽히 독립적인 구조가 아니다. 쓰레드들 사이에는 공유하는 요소들이 있다.
- 쓰레드는 공유하는 요소가 있는 관계로 컨텍스트 스위칭에 걸리는 시간이 프로세스보다 짧다.
메모리 구조 관점에서 본 프로세스와 쓰레드
프로세스와 쓰레드의 차이점을 이해하기 위해 메모리 구조 관점에서 살펴보자. 다음은 하나의 프로세스 내에서 두 개의 자식 프로세스를 생성했을 때의 메모리 구조를 보여준다.
자식 프로세스가 생성되고 난 다음에는 모든 것이 부모 프로세스와 독립적이다. 이러한 메모리 구조를 지녔기 때문에 프로세스 간에 데이터를 주고받기 위해서 IPC라는 메커니즘이 필요한 것이다.
다음은 하나의 프로세스 내에서 두 개의 쓰레드를 생성했을 때의 메모리 구조를 보여준다.
위 그림에서 보여주듯이 쓰레드를 생성할 때마다, 해당 쓰레드만을 위한 스택을 생성할 뿐 그 이외의 영역은 부모 프로세스 영역을 공유하고 있다. (부모 프로세스는 임의의 표현.)
쓰레드의 특성 1: 쓰레드마다 스택을 독립적으로 할당해 준다.
스택은 함수 호출 시 전달되는 인자, 되돌아갈 주소값 및 함수 내에서 선언하는 변수 등을 저장하기 위한 메모리 공간이다. 따라서 이 메모리 공간이 독립적이라는 뜻은 추가적인 실행 흐름을 만들 수 있다는 의미가 된다.
쓰레드의 특성 2: 코드 영역을 공유한다.
쓰레드는 자신을 생성한 프로세스가 가지고 있는 함수를 호출할 수 있다. 왜냐하면 코드 영역을 공유하기 때문이다.
위 그림에서, 프로세스는 프로그램 흐름의 첫 시작인 main 함수를 가진다. 그리고 이 영역에 또 다른 실행 흐름을 의미하는 쓰레드의 main 함수가 있다. 이는 코드 영역에 존재하는 모든 함수를 호출할 수 있다. 위 그림에서 보면 총 3개의 main 함수가 존재한다. 결과적으로 프로그램의 흐름은 총 세 개가 된다. 또한, 쓰레드는 자식 프로세스를 생성하는 것처럼 함수 호출을 통해 생성한다.
쓰레드의 특성 3: 데이터 영역과 힙을 공유한다.
쓰레드간에 힙과 데이터 영역을 공유하기 때문에 쓰레드 A가 접근할 수 있는 힙과 데이터 영역은 쓰레드 B도 접근할 수 있다. 따라서, IPC와 같은 복잡한 통신 기법은 필요가 없다. 좀 더 직관적으로 설명하면, 전역변수와 malloc 함수를 통해서 동적 할당된 메모리 공간은 공유가 가능하다.
이러한 데이터 영역과 힙의 공유는 메모리 영역을 공유하는 것이기 때문에 관련된 문제가 발생할 수 있으므로, 프로그래밍할 때 주의가 필요하다.
Windows에서의 프로세스와 쓰레드
Windows 입장에서 프로세스는 단순히 쓰레드를 담는 상자에 지나지 않는다. 때문에 실제로 프로그램의 흐름을 형성하는 것은 쓰레드이다.
또한, Windows 운영체제에 있어서 프로세스는 상태(Running, Ready, Blocked)를 지니지 않는다. 상태를 지니는 것은 프로세스가 아니라 쓰레드이다. 뿐만 아니라, 스케줄러가 실행의 단위로 선택하는 것도 프로세스가 아닌 쓰레드이다.
+컨텍스트 스위칭이 빨라진 쓰레드
쓰레드는 코드 영역을 공유함으로 인해 호출할 수 있는 함수들을 공유하게 되는 것이지, pc와 같은 레지스터 정보들까지 공유하는 것은 아니다. 또한, sp와 fp의 경우 스택은 쓰레드별로 독립적이기에 공유되지 않는다. 따라서, 이와 관련한 컨텍스트 스위칭은 기존과 비슷하다.
따라서, 컨텍스트 스위칭이 빨라진 것을 이해하기 위해서는 메모리 관리 측면에서 생각해야만 한다. 캐쉬는 CPU에서 한 번 이상 읽어 들인 메인 메모리의 데이터를 저장하고 있다가 CPU가 다시 그 메모리에 저장된 데이터를 요구할 때, 메인 메모리를 통하지 않고 바로 값을 전달해 주는 역할을 한다. 이때 컨텍스트 스위칭이 일어났다고 가정하면, 프로세스의 경우 캐쉬의 저장해 놓은 모든 데이터를 버리고, 새로 쌓아야 한다. 하지만 쓰레드의 경우 캐쉬를 비울 필요가 없게 된다.
위 그림을 보면, 프로세스 B 안에 두 개의 쓰레드가 존재한다. 이 둘은 하나의 프로세스 내에 존재하므로 별개의 쓰레드가 아니다. 이 둘 사이에서 진행되는 컨텍스트 스위칭은 속도가 빠르다. 그러나 프로세스 A의 쓰레드가 실행되는 도중에 프로세스 B의 쓰레드로 실행을 옮기는 과정에서는 기존에 알고 있던 컨텍스트 스위칭이 발생한다.
보통 Windows에서의 프로세스 컨텍스트 스위칭은 서로 다른 프로세스 내에 존재하는 쓰레드 사이에서의 컨텍스트 스위칭을 말한다. 추가적으로, Windows에서는 쓰레드가 존재하지 않는 프로세스는 없으며, 메인 함수를 호출하는 쓰레드를 main 쓰레드라 한다.
02. 쓰레드 구현 모델에 따른 구분
이번에는 쓰레드의 구현 원리에 대해서 이야기하자.
커널 레벨(Kernel Level) 쓰레드와 유저 레벨(User Level) 쓰레드
첫 번째 경우로, 쓰레드를 생성해 주는 대상은 커널일 수 있다. 이러한 경우 운영체제가 제공하는 시스템 함수 호출을 통해서 쓰레드 생성을 요구해야 한다. 이렇듯 프로그래머 요청에 따라 쓰레드를 생성 및 스케줄링하는 주체가 커널인 경우, 이를 가리켜 커널 레벨(Kernel Level) 쓰레드라 한다.
위 그림은 커널 레벨 쓰레드의 특성을 보여주고 있다. 그림에 나타난 유저 영역은 사용자에 의해서 할당되는 메모리 공간을 의미한다. 간단히 말해서, 지금까지 우리가 다룬 코드 영역, 데이터 영역, 스택 및 힙 영역을 가리켜 유저 영역(User 영역)이라 한다.
또한, 커널 영역은 하나의 프로세스에게 할당된 총 메모리 공간 중에서 유저 영역을 제외한 나머지 영역을 커널 영역이라 한다. 운영체제 역시 실행되기 위해 메모리에 올라가야 하고, 일반 프로그램처럼 변수 선언 및 동적 할당을 하기도 한다. 이처럼 운영체제라는 하나의 소프트웨어를 실행시키기 위해서 필요한 메모리 공간을 커널 영역(kernel 영역)이라 한다.
다시 돌아와서, 쓰레드에게 일을 시키기 위한 프로그램 코드는 유저 영역에 존재하며, 스케줄러와 쓰레드 정보는 커널 영역에 존재한다. 이것이 바로 커널 레벨 쓰레드의 유형이다. 오늘날 대부분의 운영체제는 이를 기반으로 하고 있다.
두 번째 경우로, 유저 레벨(User Level) 쓰레드 모델이다. 멀티 프로세스 운영체제라고 해서 커널이 기본적을 쓰레드를 지원하는 것은 아니다. 이처럼 커널에서 쓰레드 기능을 지원하지 않을 때 생각할 수 있는 것이 유저 레벨 쓰레드이다. 커널에 의존적이지 않은 형태로 쓰레드의 기능을 제공하는 라이브러리를 활용할 수 있는데, 이것이 바로 유저 레벨 쓰레드이다.
유저 레벨 쓰레드 모델은 쓰레드를 지원하지 않기 때문에 스케줄러가 스케줄링하는 대상은 프로세스이며, 쓰레드를 스케줄링하는 스케줄러는 유저 영역에서 실행된다.
결론적으로, 유저 레벨 쓰레드와 커널 레벨 쓰레드는 기능의 제공 주체가 누구냐에 달려 있다.
+ 커널(Kernel) 영역
운영체제도 함수와 코드영역, 전역변수 선언 등이 당연히 존재한다.
커널 모드(Kernel Mode)와 유저 모드(User Mode)
유저 레벨 쓰레드와 커널 레벨 쓰레드의 장단점을 이해하기 위해 Windows 운영체제가 동작하는 두 가지 모드에 대해 알아보자. Windows는 동작할 때 커널 모드와 유저 모드 중 한 가지 모드로 동작한다. 그전에 지금까지의 내용을 정리하면 다음과 같다.
메모리는 활용 대상에 따라서 유저 영역과 커널 영역으로 나뉜다. 유저 영역은 사용자가 구현한 프로그램 동작 시 사용하게 되는 메모리 영역이고, 커널 영역은 운영체제 동작 시 사용하게 되는 메모리 영역이다. 그리고 커널이 쓰레드를 지원할 경우 쓰레드 관리가 커널 영역에서 이뤄지기 때문에 커널 레벨 쓰레드 모델이라 하고, 커널이 지원하지 않을 경우에 라이브러리를 통해서 제공받는데, 이러한 경우에는 유저 영역에서 쓰레드의 관리가 이뤄지기 때문에 유저 레벨 쓰레드 모델이라 한다.
커널 영역은 운영체제를 담고 있기 때문에 유저 영역에 비해 상대적으로 중요하다. 하지만 C언어와 같은 메모리 접근에 용이한 언어를 사용할 경우, 커널 영역의 주소값을 가지고 메모리에 접근하는 것을 주의해야 한다. 이러한 문제를 막기 위해 Windows는 커널 모드와 유저 모드를 제공한다.
일반적인 프로그램은 기본적으로 유저 모드에서 동작한다. 그러다가 커널 영역에서 실행이 이뤄져야 할 경우에는 커널 모드로의 전환이 일어나는 것이다. 예를 들어, 여러 개의 프로세스들이 실행 중에 있을 때, 정해진 타임 슬라이스가 지나 스케줄러가 동작하려 한다. 이때 커널 모드로의 전환이 일어나는 것이다. 왜냐하면 스케줄러는 커널의 일부에 해당하기 때문이다.
이렇듯이 커널 모드와 유저 모드의 차이점은 프로세스가 유저 모드에서 동작할 때에는 커널 영역으로의 접근이 금지된다. 반면에 커널 모드에서 동작할 때에는 모든 영역의 접근이 허용된다. 또한, 이러한 모드의 전환은 시스템에 부담을 주는 일이다.
+ 유저 모드와 커널 모드를 제공하는 것은 운영체제가 아닌 프로세서이다. 즉 메모리 보호 기능이 CPU에 달려 있다.
커널 레벨 쓰레드와 유저 레벨 쓰레드의 장단점
커널 레벨 쓰레드의 장점 및 단점
장점 : 커널에서 직접 제공해 주기 때문에 안정성과 다양한 기능성이 제공된다.
단점 : 커널에서 제공해 주는 기능이기 때문에 유저 모드에서 커널 모드로의 전환이 자주 일어난다. 이는 성능 저하로 이어진다.
유저 레벨 쓰레드의 장점 및 단점
장점 : 커널은 쓰레드의 존재조차 모른다. 오로지 유저 모드로 동작하기 때문에 모드의 전환이 필요 없다. 따라서 성능이 좋다.
단점 : 하나의 프로세스 내에 총 3개의 쓰레드 A, B, C가 있을 때, A 쓰레드가 시스템 함수를 호출하여 커널에 의해 블로킹되면, 해당 프로세스 전부가 블로킹 되어 B와 C도 실행되지 않게 된다. 이러한 문제를 해결하기 위해서는 복잡한 프로그래밍이 요구된다.
참고 자료:
윤성우. 『뇌를 자극하는 윈도우즈 시스템 프로그래밍』.한빛미디어, 2007.
'독서 > [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]' 카테고리의 다른 글
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 13. 쓰레드 동기화 기법 1 (2) | 2023.11.05 |
---|---|
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 12. 쓰레드의 생성과 소멸 (2) | 2023.10.29 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 10. 컴퓨터 구조에 대한 세 번째 이야기 (3) | 2023.10.08 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 09. 스케줄링 알고리즘과 우선순위 (3) | 2023.10.03 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 08. 프로세스간 통신(IPC) 2 (2) | 2023.10.03 |