Thread
컴퓨터가 발전하면서 컴퓨터로 할 수 있는 것들이 많아지고, 하나의 프로그램이 처리해야 할 작업의 양과 종류 또한 증가했습니다. 이제 하나의 프로세스만으로 늘어나는 작업량을 감당할 수 없게 되었습니다. 이번 글에서는 Thread(이하 스레드)에 대하여 알아봅니다.
스레드란 무엇인가?
스레드는 프로세스 내부의 흐름이다. 프로그램의 실행을 물의 흐름으로 표현하자면 스레드는 하나의 물줄기를 가리키는 개념이다. 이 물줄기는 하나일 수도 있고, 여럿일 수도 있다. 쉬운 이해를 위해 스레드가 구체적으로 무엇인지 알아보기 전에, 스레드와 관련된 여러가지 구분을 살펴보자. 스레드를 넘어 운영체제나 컴퓨터에 관한 전반적인 이해도를 높이는 일에 도움이 될 것이다.
Single-Threaded vs. Multi-Threaded
우선 스레드를 기준으로 모든 프로그램을 싱글스레드 프로그램과 멀티스레드 프로그램으로 나눌 수 있다. 그런데 멀티스레드 프로그램의 프로세스가 여러 개의 스레드를 가지고 있다고 해서 실제로 모든 스레드가 동시에 작동하는 것은 아니다.
- 첫번째 종류의 프로그램은 실행되었을 때 그 안에 단 한 개의 스레드를 가지고 있다. 여기에 속하는 프로그램이 실행되어 프로세스가 만들어질 때 그 안에는 오직 하나의 스레드가 있고, 프로세스의 시작부터 종료까지 모든 과정이 하나로 이어져 있다. 이런 프로그램을 가리켜 싱글스레드 프로그램Single-threaded program 이라 한다.
- 두번째 종류의 프로그램은 실행되었을 때 2개 이상의 스레드를 가지고 있다. 따라서 프로그램이 실행되어 프로세스가 만들어질 때, 프로세스 내부에서 2개 이상의 스레드가 동시에 작업을 수행한다. 이런 프로그램을 가리켜 멀티스레드 프로그램Multi-threaded program 이라 부른다.
- 실제로 프로세스를 실행하는 것은 프로세서Processor 안에 있는 코어인데, 코어는 한 번에 하나의 작업만 할 수 있다.[1] 이런 제약 조건이 있는 상황에서 멀티스레드 프로그램이 의도대로 동시에 여러가지 일을 실행할 수 있도록 하는 첫번째 방법은 여러 개의 코어를 사용하여 각 코어가 작업을 동시에 처리하도록 하는 것이다.
- 두번째 방법은 일종의 눈속임이다. 하나의 코어가 한번에 한가지 일밖에 하지 못하는 제약 조건을 극복하기 위해 한가지 작업을 하던 도중에 그 작업을 중단하고 다른 작업을 하는 과정을 반복하는 방법이다.
Concurrency & Parallelism
앞서 나온 두 가지 방법 중 어느 것을 사용하던 상관없이 여러 개의 작업이 이루어지고 있다는 것을 알 수 있는데, 이것이 바로 동시성Concurrency 의 정의다.[2] 그리고 실제로는 오직 첫번째 방법을 사용할 때만 여러 개의 계산이 동시에 이루어지고 있는데, 이것이 병렬성Parallelism 의 정의다. 그렇기 때문에 병렬성은 동시성의 하위 개념이다. 병렬성을 충족하면 동시성을 자동으로 충족하지만, 두번째 방법에서 알 수 있듯이, 동시성을 충족한다고 해서 병렬성을 충족하는 것은 아니다.
User-Level vs Kernel-Level
스레드라고 해서 모두 같은 것은 아니다. 다른 글에서 본격적으로 다루겠지만, 스레드를 직접 관리하기 위해서는 스레드 라이브러리가 필요하다. 스레드 라이브러리를 통해 우리가 직접 스레드를 만들고, 실행시키고, 종료시킬 수 있다. 이런 종류의 스레드를 사용자 수준 스레드user-level thread라 부른다. 한편, 운영체제의 가장 깊은 곳에는 항상 실행 중이어야 하고 사용자가 마음대로 접근해서는 안되는 핵심 영역이 있는데, 이 영역을 커널kernel이라 부른다. 커널은 스레드 라이브러리보다 더 깊은 곳에 있고 바로 이 영역에서 스레드를 직접 다루는데, 이런 종류의 스레드를 커널 수준 스레드kernel-level thread라 부른다.
Thread-safe
어떤 스레드가 어떤 타이밍에 활동할지는 예측하기 어렵기 때문에, 프로그램의 실행 양상은 실행할 때마다 달라질 수 있다. 스레드-안전한thread-safe 코드는 스레드가 어떻게 작동하든 상관없이 같은 결과를 보장하는 코드이며, 이를 위해서는 프로그래머가 멀티스레드 프로그램에서 발생할 수 있는 각종 경합 상황을 잘 다루어야 한다.
스레드의 생성과 종료
이처럼 스레드는 프로세스 내부에 존재하는 작은 일꾼으로써, 여러 개가 있다면 프로세스가 처리해야 하는 작업을 나누어 맡는다. 이제 이런 스레드가 구체적으로 어떤 것인지 스레드의 생명 주기를 따라가며 알아보자. 스레드는 과연 어떻게 생겼고, 어떻게 태어나고 무슨 일을 하고 어떻게 없어지는 걸까?
스레드의 생성
모든 프로세스는 최소한 한 개의 스레드를 가지는데, 이 스레드가 바로 프로세스 실행의 중심축인 메인 스레드main thread다. 프로세스는 언제나 메인 스레드로 시작하며, 새로운 스레드가 생성되면 새로 생성된 스레드가 메인 스레드에 연결된다. 이 과정에서 새로 연결된 스레드는 자식 스레드, 메인 스레드는 부모 스레드가 된다. 그래서 메인 스레드로부터 나머지 모든 스레드가 비롯되며, 메인 스레드를 제외한 모든 스레드는 부모 스레드를 가진다.
모든 스레드는 프로세스에 의해 생성되고, 그렇기 때문에 살아있는 동안 해당 프로세스의 소유이며 다른 프로세스와 공유하거나 스레드의 소유권을 다른 프로세스로 넘길 수 없다. 모든 스레드는 같은 프로세스 ID(PID)를 공유하며, 스레드를 소유한 프로세스의 모든 속성을 상속받고, 프로세스 내부에서 고유한 스레드 ID(TID)를 가진다. 또한 스레드가 특정 시그널에만 반응할 수 있도록 모든 스레드에는 지정된 시그널 마스크signal mask[3]가 있어서, 스레드는 필요한 신호만 받아 잘못된 상태 변화를 거치지 않는다.
스레드의 구조
이렇게 생성된 스레드는 프로세스 안에서 위와 같은 구조로 존재하며 하나의 스레드는 고유의 PC[4], SP[5], 그리고 스택 메모리 영역[6]을 가진다. 만약 한 프로세스 안에 존재하는 스레드가 여러 개라면 모든 스레드는 일반적으로 해당 프로세스의 메모리를 사용한다.[7] 프로세스와 스레드를 구분하는 가장 중요한 특징이 바로 이 공유 메모리의 존재 유무라고 말할 수 있을 정도로 멀티스레드 프로세스에서 공유 메모리를 사용할 수 있다는 것은 스레드의 어마어마한 특권이며, 덕분에 스레드는 프로세스 간 소통에 사용하는 모든 방법을 같이 사용하는 것에 더해 다른 스레드와 휠씬 빠르게 정보를 공유할 수 있다.
결합과 분리
그러나 스레드가 생성되었다고 해서 전부 실행되는 것은 아니다. 스레드가 실행되기 위해서는 CPU를 사용할 수 있도록 허가를 받아야 하는데, 이 문제는 스케줄링에 달려 있어서 새 스레드가 언제 CPU를 사용하게 될지는 예측할 수 없다. 이때 해당 스레드의 부모 스레드가 종료된다면, 해당 스레드는 실행도 못해보고 죽게 된다. 메인 스레드는 자기와 연결된 모든 스레드 중에서 가장 마지막에 종료되어야 하기 때문에, 새로 생성된 스레드는 메인 스레드와 결합하지 않는다면 실행되지 못한다.
기본적으로 모든 스레드는 메인 스레드와 결합할 수 있고, 또 분리될 수 있다. 다만 한 번 분리된 스레드를 다시 결합할 수 없으며, 분리된 스레드는 독자적으로 실행된다. 또한 스레드 ID는 스레드끼리 구분하기 위한 개념이기 때문에, 메인 스레드로부터 분리된 스레드가 종료할 경우 분리된 스레드의 ID를 새로운 스레드가 태어날 때 다시 사용할 수 있다.
스레드 종료
스레드의 생명 주기는 해당 스레드가 속한 프로세스에 달려 있다. 프로세스가 강제로 종료kill되거나 정상적으로 종료terminate되면, 해당 프로세스의 메인 스레드에 속하는 모든 스레드도 종료된다. 그러나 메인 스레드와 어떤 스레드를 분리detach할 수 있기 때문에, 만약 분리된 스레드가 있다면 프로세스는 메인 스레드가 종료되어도 종료되지 않고 모든 스레드가 종료되기를 기다린다.
더보기
참고 자료 & 더보기
참고 자료
- Process Signal Mask (The GNU C Library)
- Amini, K. (2022). 전문가를 위한 C (박지윤, Trans.; 1st ed.). 한빛미디어.
- Silberschatz, A., Galvin, P. B., & Gagne, G. (2018). Operating system concepts (10th ed.). Wiley.
- Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. (2014). Operating Systems: Three Easy Pieces (Version 0.8). Arpaci-Dusseau Books, Inc., http://www.ostep.org
더보기
한 번에 한가지 작업만 다룰 수 있는 프로세싱 유닛Processing Unit 는 작업을 처리하는 하드웨어의 최소 단위로, 현대 컴퓨터에서는 코어가 실제 프로세싱 유닛에 해당한다. ↩︎
동시성과 같은 개념으로는 멀티태스킹Multitasking 이 있다. ↩︎
시그널 마스크는 프로세스 혹은 스레드가 받지 않는 신호들의 모음으로, 신호를 필터링할 때 사용한다. ↩︎
Program Counter, 다음으로 실행할 명령어의 주소를 저장하는 레지스터이며 컴퓨터가 프로그램의 실행 순서 중 현재 어디에 있는지를 나타낸다. ↩︎
Stack Pointer, 가장 최근에 호출한 함수의 위치를 저장하는 레지스터이며 호출 스택을 추적하는 것이 목적이다. ↩︎
경우에 따라서는 스레드끼리 공유하기도 한다. ↩︎
일반적으로 프로세스의 힙 세그먼트를 사용한다. ↩︎