Thread

컴퓨터가 발전하면서 컴퓨터로 할 수 있는 것들이 많아지고, 하나의 프로그램이 처리해야 할 작업의 양과 종류 또한 증가했습니다. 이제 하나의 프로세스만으로 늘어나는 작업량을 감당할 수 없게 되었습니다. 이번 글에서는 Thread(이하 스레드)에 대하여 알아봅니다.


스레드란 무엇인가?

스레드는 프로세스 내부의 흐름이다. 프로그램의 실행을 물의 흐름으로 표현하자면 스레드는 하나의 물줄기를 가리키는 개념이다. 이 물줄기는 하나일 수도 있고, 여럿일 수도 있다. 쉬운 이해를 위해 스레드가 구체적으로 무엇인지 알아보기 전에, 스레드와 관련된 여러가지 구분을 살펴보자. 스레드를 넘어 운영체제나 컴퓨터에 관한 전반적인 이해도를 높이는 일에 도움이 될 것이다.

Single-Threaded vs. Multi-Threaded

우선 스레드를 기준으로 모든 프로그램을 싱글스레드 프로그램과 멀티스레드 프로그램으로 나눌 수 있다. 그런데 멀티스레드 프로그램의 프로세스가 여러 개의 스레드를 가지고 있다고 해서 실제로 모든 스레드가 동시에 작동하는 것은 아니다.

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]가 있어서, 스레드는 필요한 신호만 받아 잘못된 상태 변화를 거치지 않는다.

스레드의 구조

OS&Architecture/ThreadStructure.png|A Simple Structure of A Multithreaded Program

이렇게 생성된 스레드는 프로세스 안에서 위와 같은 구조로 존재하며 하나의 스레드는 고유의 PC[4], SP[5], 그리고 스택 메모리 영역[6]을 가진다. 만약 한 프로세스 안에 존재하는 스레드가 여러 개라면 모든 스레드는 일반적으로 해당 프로세스의 메모리를 사용한다.[7] 프로세스와 스레드를 구분하는 가장 중요한 특징이 바로 이 공유 메모리의 존재 유무라고 말할 수 있을 정도로 멀티스레드 프로세스에서 공유 메모리를 사용할 수 있다는 것은 스레드의 어마어마한 특권이며, 덕분에 스레드는 프로세스 간 소통에 사용하는 모든 방법을 같이 사용하는 것에 더해 다른 스레드와 휠씬 빠르게 정보를 공유할 수 있다.

결합과 분리

그러나 스레드가 생성되었다고 해서 전부 실행되는 것은 아니다. 스레드가 실행되기 위해서는 CPU를 사용할 수 있도록 허가를 받아야 하는데, 이 문제는 스케줄링에 달려 있어서 새 스레드가 언제 CPU를 사용하게 될지는 예측할 수 없다. 이때 해당 스레드의 부모 스레드가 종료된다면, 해당 스레드는 실행도 못해보고 죽게 된다. 메인 스레드는 자기와 연결된 모든 스레드 중에서 가장 마지막에 종료되어야 하기 때문에, 새로 생성된 스레드는 메인 스레드와 결합하지 않는다면 실행되지 못한다.

기본적으로 모든 스레드는 메인 스레드와 결합할 수 있고, 또 분리될 수 있다. 다만 한 번 분리된 스레드를 다시 결합할 수 없으며, 분리된 스레드는 독자적으로 실행된다. 또한 스레드 ID는 스레드끼리 구분하기 위한 개념이기 때문에, 메인 스레드로부터 분리된 스레드가 종료할 경우 분리된 스레드의 ID를 새로운 스레드가 태어날 때 다시 사용할 수 있다.

스레드 종료

스레드의 생명 주기는 해당 스레드가 속한 프로세스에 달려 있다. 프로세스가 강제로 종료kill되거나 정상적으로 종료terminate되면, 해당 프로세스의 메인 스레드에 속하는 모든 스레드도 종료된다. 그러나 메인 스레드와 어떤 스레드를 분리detach할 수 있기 때문에, 만약 분리된 스레드가 있다면 프로세스는 메인 스레드가 종료되어도 종료되지 않고 모든 스레드가 종료되기를 기다린다.

더보기


참고 자료 & 더보기

참고 자료

더보기


  1. 한 번에 한가지 작업만 다룰 수 있는 프로세싱 유닛Processing Unit 는 작업을 처리하는 하드웨어의 최소 단위로, 현대 컴퓨터에서는 코어가 실제 프로세싱 유닛에 해당한다. ↩︎

  2. 동시성과 같은 개념으로는 멀티태스킹Multitasking 이 있다. ↩︎

  3. 시그널 마스크는 프로세스 혹은 스레드가 받지 않는 신호들의 모음으로, 신호를 필터링할 때 사용한다. ↩︎

  4. Program Counter, 다음으로 실행할 명령어의 주소를 저장하는 레지스터이며 컴퓨터가 프로그램의 실행 순서 중 현재 어디에 있는지를 나타낸다. ↩︎

  5. Stack Pointer, 가장 최근에 호출한 함수의 위치를 저장하는 레지스터이며 호출 스택을 추적하는 것이 목적이다. ↩︎

  6. 경우에 따라서는 스레드끼리 공유하기도 한다. ↩︎

  7. 일반적으로 프로세스의 힙 세그먼트를 사용한다. ↩︎