Process와 Thread의 차이
1. 들어가며
오늘 포스트는 Process(이하 “프로세스”), Thread(이하 “스레드”)의 차이에 대해 다룬다. 개발 면접 시 단골 질문이기에 관련된 수많은 포스트들이 있는데 내가 필요할 때 다시 돌아와서 볼 수 있는 나만의 내용을 정리하고 싶어 작성한다.
포스트의 순서는
- 프로세스에 대해 먼저 알아보고,
- 스레드에 대해 알아본다.
- 다음은 이 둘의 차이를 파악한다.
2. 프로세스
프로세스를 이해하기에는 먼저 프로그램(Program)과 비교하면 편하다.
“프로그램은 실행가능한 명령어(instruction)의 집합”이다. 프로그램은 보통 디스크에 저장되어 컴파일된 바이너리 이미지 형태일 수도 있고, 파이썬 스크립트 같이 해석되는(Interpret) 고급어 형태일 수도 있다. 프로그램의 정의는 꽤나 포용적이기 때문에 당신의 컴퓨터에 설치된 포토샵 파일도 프로그램이고 또 내가 짤 수 있는 파이썬 구구단 출력 스크립트 파일도 프로그램이다. 중요한 건 디스크에 저장된 실행 가능한 명령어의 집합인지의 여부이다. 후자가 면접자들에게 깊은 인상을 남길 수 있을지는 모르겠지만 말이다.
프로세스는 여기서 출발한다. 프로그램은 애시당초 누군가에게 쓰여지기를 목적으로 개발되었고 실제로 누군가에 의해 실행된다. “프로세스는 메모리에 적재(load)되어 실행되고 있는 프로그램”을 말한다. 정적인 프로그램과 달리 프로세스는 실제 실행 중인 프로그램을 일컫기 때문에 동적이라고 표현하기도 한다.
프로세스를 “프로그램의 인스턴스”라고 표현하기도 하는데 실제 객체 지향의 클래스와 인스턴스와 비교/대조하면 재밌다. 프로그램과 프로세스의 관계와 클래스와 인스턴스의 관계의 공통점은 한 클래스가 여러 인스턴스를 생성할 수 있는 것과 같이 한 프로그램에서 실행되는 여러 프로세스가 동시에 존재할 수 있다. 윈도우즈 유저라면 메모장을 여러 개 실행시킴으로써 이를 증명할 수 있다. 차이는 프로그램은 클래스처럼 다른 프로그램을 상속하지는 않는다. 라이브러리나 모듈이라는 이름으로 사용할 수는 있지만.
프로세스는 커널에 의해 직접 관리되는데 커널 메모리 안에는 각 프로세스마다 관리하고 있는 프로세스에 대한 데이터들이 있다. 이 정보는 Process Control Block(이하 “PCB”)이라고 하는 자료구조 안에 있는데 커널 스케쥴러가 프로세스를 제어하는 데 필요한 정보들이 담겨 있다. PCB는 다음과 같은 정보와 자원을 포함한다.
- 프로그램과 관련된 실행가능한 기계어 이미지
- 운영체제와 관련해 할당된 자원의 식별자(유닉스의 File Descriptor, 윈도우즈의 Handle 등)
- 프로세스의 소유자 등 프로세스와 관련된 Permission 정보
- Context라고 일컬어지는 프로세스 상태. 물리적 메모리 주소나 CPU 내 레지스터의 내용, 실행 중인 명령어를 지정하는 Program Counter 등을 포함한다.
- 실행되는 프로세스에 대한 메모리 주소
이때 커널 메모리 안에서 관리되는 PCB 정보가 아닌 유저가 사용하는 메모리 공간 상의 프로세스 정보는 다음 4가지 분류로 다시 나뉜다.
- Code(text): 프로그램의 실제 코드를 저장
- Data: 프로세스가 실행될 때 정의된 전역 변수, Static 변수들을 저장
- Heap: 프로세스 런타임 중 동적으로 할당되는 변수들을 저장(함수 내에서 할당되는 변수 등)
- Stack: 함수에서 다른 함수를 실행하는 등의 서브루틴들의 정보를 저장(재귀와 스택이 관련 있는 이유)
위 그림처럼 메모리 상의 프로세스들은 4가지의 정보 집합으로 구성되며 이 네 가지 정보 분류는 이후 프로세스와 스레드의 차이를 다룰 때 다시 나온다.
운영체제는 각각의 프로세스는 독립적으로 관리하기 때문에 서로 다른 프로세스가 겹칠 일이 없고, 또 사용 자원 영역 등이 겹치는 일이 발생해서도 안 된다. 가령 한 프로세스가 다른 프로세스의 정보 한 부분을 변경하면 그 프로세스에 치명적인 오류가 날지도 모르는 일이기 때문이다. 한 가지 예외로 같은 프로그램의 프로세스들은 Code 영역은 공유한다. 내용이 동일한 프로그램의 코드를 여러 개 복사해서 프로세스마다 가지고 있는 것 보다는 메모리 상의 코드 공간을 주소로 참조하는 것이 상식적으로 낫다.
대부분의 운영체제는 독립적인 각 프로세스가 다른 프로세스의 정보를 변경하는 것을 극도로 주의하고 있으며 필요할 경우 최소한의 인터페이스를 제공해 소통할 수 있도록 하고 있다. 이런 프로세스간 소통을 Inter Process Communication(IPC)이라고 한다. 프로세스간 통신의 예로는 다음과 같은 유닉스 파이프라이닝이 있을 수 있겠다.
$ cat many-names | sort | uniq
참고로 프로세스간 통신은 꼭 같은 컴퓨터에서의 프로세스를 가정할 이유는 없다. 다른 컴퓨터에 위치한 두 프로세스가 통신할 수도 있는데 이때 운영 시스템 등이 다를 수 있기 때문에 통신을 위해 그 유명한 ‘Protocol’이 필요해진다.
2.1. 잠깐, Multitasking과 Context switch
이후 내용을 진행하기에 앞서 운영체제와 관련해서 Multitasking과 Context switch에 대해 정리할 필요가 있다. 이들은 운영체제에서 프로세스를 운영하는 것과 관련이 있다.
나는 지금 음악을 들으면서 포스팅을 하고 있다. 경우에 따라서는 검색도 하고 영어사전도 찾아가면서 여러 작업을 동시에 진행하는데 이런 작업들, 음악 재생, 파일의 내용 변경과 디스크 저장, HTTP 통신 등은 다 프로세스들에 의해 진행되고 있다. 이렇게 한 컴퓨터에서 여러 가지 작업을 동시에 하는 것을 Multitasking이라고 하며 현대적인 운영체제는 모두 Multitasking을 지원한다.
근데 참 신기하다. 컴퓨터가 대단하다고는 하지만 어떻게 수많은 작업을 동시에 하고 있는 것일까? 실은 프로세스들은 동시에 실행되고 있지 않다. Time sharing이라는 기법 안에서 여러 프로세스들은 “동시”라고 느껴질만큼 매우 짧은 순간 동안 작업하고(CPU를 점유하고) 다른 프로세스에 CPU 자원을 양보하는 것이다. 이렇게 작업이 전환되는 속도가 매우 짧아 인간은 이를 프로세스들이 동시에 진행되고 있다고 믿게 된다.(원래 인간은 사실보다는 사실이라고 믿는 것을 더 좋아한다.)
이때 생기는 문제가 있다. 어떤 프로세스에서 점유하던 CPU 자원을 멈추고 다른 프로세스에 양보했다고 치자. 다시금 자기 차례가 되었을 때 마지막으로 어떤 작업 중이었는지 어떻게 기억해야 할까? 이는 중요한 이슈다.
앞서 작업 중인 프로세스에 대한 정보를 Context라고 했다. 작업을 중단했을 때의 Context 정보는 프로세스가 전환될 때 PCB에 저장한다. 이후 자신의 차례가 다시 왔을 때 PCB에 저장된 상태에서 작업을 재개하면 된다.
이렇게 프로세스가 전환되면서 Context를 전환하는 것을 Context Switch라고 한다. 이때 작업 중이던 Context를 저장하고 새로운 Context를 로드하면서 CPU 레지스터 상태 변환, 스택 포인터 추적, 명령어를 추적하는 Program Counter 등에 대한 작업을 처리하기 때문에 오버헤드가 발생한다. 하지만 그럼에도 멀티태스킹에 대한 수요가 확실하기 때문에 많은 운영체제가 Context Switch를 최적화하는 데 집중하고 있다.
3. 스레드
내 생각에 스레드는 이 단 하나의 문장으로 정의하면 된다.
“프로세스 내에서 실행되는 흐름”
파이썬 스크립트 파일을 보자. 그 파일을 실행시키면 한 줄 한 줄 파일이 실행될 것을 우리는 안다. 그렇게 실행되는 일련의 흐름이 곧 스레드다.
일반적으로 하나의 프로세스는 하나의 스레드로 시작되며 이를 메인 스레드라고 한다. 스레드를 추가로 생성하지 않는 한 모든 프로그램은 메인 스레드에서 실행된다.
MS-Dos 등 싱글스레딩 운영체제와 달리 현대적인 운영체제에서는 하나의 프로세스 내에 여러 스레드가 동시에 존재할 수 있는(Concurrent) 멀티스레딩(Multi-threading)을 지원한다. 이 말은 곧 실행 중인 프로세스 내에 여러 흐름이 존재할 수 있다는 것이 되는데 애초에 이런 일이 왜 필요할까?
생각을 해보자. 우리가 일반적으로 만드는 프로그램은 한 줄 한 줄 순서대로 실행한다. 문제를 구성하는 부분문제들이 있고 각 부분문제들에 대한 해답이 순차적으로 연결된 구조를 가질 것이다. 가령 우리가 json 파일을 DB처럼 쓰고 있는데 내용을 업데이트해야 한다고 치자. 우리의 작업은 1. 파일을 읽어와 2. 내용을 변경한 뒤 3. 디스크에 저장하는 세 가지의 부분작업이 순차적으로 연결되어 있는 것이다. 각 작업간 의존관계가 있기 때문에 어쩔 수 없기는 하지만, 이런 순차적인 방식의 단점은 한 작업이 오래 걸리면 전체 프로그램이 지연되는 병목현상이 생길 수 있다는 점이다.
하지만 세상에는 이렇게 모든 작업이 선형적으로 연결되어 실행되지 않아도 되는 경우도 있다. 한 프로세스 내에서 서로 순서상 의존하지 않는 작업이 다른 작업의 종료를 기다릴 이유가 없는 것이다. 이때 실행되는 흐름, 스레드를 여러 개 두면 병목현상에 걸리지 않고 전체 작업시간을 줄일 수 있다.
운영체제적으로는 한 프로세스 안의 스레드들은 스택 공간을 제외한 프로세스의 나머지 공간과 시스템 자원을 공유하는데 이는 스레드가 관련된 여러 장점과 단점을 갖게 한다.
스레드를 사용할 때의 장점은 다음과 같다.
- 프로세스 간 통신에 비해 스레드 간 통신이 훨씬 간단하다.
- 서로 공유하는 변수를 변경하기만 하면 되기 때문이다. 반면 프로세스 간 통신은 그 위험성으로 까다롭게 관리된다.
- 시스템의 자원 소모가 줄어든다.
- 기존 프로세스의 자원을 다른 스레드와 공유하기 때문에 자원을 새로 할당하지 않아도 된다.
- 전체 응답 시간이 단축된다.
- 시간도 자원이기에 오버헤드가 줄어들어 전체 응답이 짧아진다. 또 병목이 걸리는 작업과 다른 작업을 구분할 수 있어 전체 실행시간을 줄일 수 있다.
위와 같은 장점은 왜 웹 서버가 각각의 HTTP 통신을 멀티프로세스가 아닌 멀티스레드로 구현하는지를 설명한다. 각 통신을 고유한 자원을 할당해야 하고 서로 간 통신도 까다로운 프로세스로 구현하고 싶지는 않을테니까 말이다.
하지만 스레드에는 단점도 있다.
- 여러 스레드를 이용하는 프로그램을 작성하는 경우에는 설계를 신경써야 한다. 미묘한 시간 차나 잘못된 변수를 공유함으로써 문제가 발생할 수 있다.(더 큰 자유에는 더 큰 책임이 따른다)
- 디버깅이 어렵다.
3. 프로세스와 스레드의 차이
프로세스와 스레드의 근본적인 차이는 프로세스는 운영체제로부터 독립된 시간, 공간 자원을 할당 받아 실행된다는 점이고, 스레드는 한 프로세스 내에서 많은 자원을 공유하면서 병렬적으로(Concurrently) 실행된다는 것이다. 다른 차이는 모두 이 근본적인 차이에서 비롯된다.
이로부터 파생되는 여러 차이는 다음과 같다.
먼저 프로세스는 보다 독립적이다. 서로 구분되는 자원을 할당 받아 정말 필요한 경우가 아니면 다른 프로세스에 영향을 미치지 않고 실행된다. 반면 스레드는 프로세스의 하위 집합으로 여러 스레드가 같은 프로세스 자원을 공유하기 때문에 독립적이지 않다. 같은 의미로 프로세스는 보유한 자원에 대한 별개의 주소 공간을 갖지만 스레드는 이 주소 공간을 공유한다.
프로세스간 통신은 스레드간 통신보다 어렵다. 프로세스는 오직 시스템이 제공하는 IPC 메커니즘을 통해서만 통신할 수 있고 시스템에 의해 관리되기 때문에 상대적으로 안전하다. 반면에 스레드는 단순히 공유 변수 수정만으로도 스레드간 통신을 구현할 수 있어 통신이 매우 용이하지만, 안전한 프로그램을 만들기 위해서는 신중해야 한다.
Context Switch에 있어서도 프로세스보다 스레드가 “일반적으로” 더 빠르고 자원소모가 적다. 프로세스는 Switch될 때의 Context를 PCB 등에 저장하는 등 오버헤드가 발생하는데 스레드는 그런 부하가 적다. 근데 이 부분은 조금 조심해야 한다. 압도적으로 스레드 Switching이 더 저렴하다는 의견이 있는 반면 운영체제나 배포판에 따라, 프로세스의 환경에 따라 거의 차이가 없을 수도 있다는 의견 등이 분분하다.
이 둘의 차이를 표로 한 번 정리해보자.
차이 | 프로세스 | 스레드 |
---|---|---|
자원 할당 여부 | 실행 시마다 새로운 자원을 할당 | 자신을 실행한 프로세스의 자원을 공유 |
자원 공유 여부 | 일반적으로 자원을 공유하지 않는다. 같은 프로그램의 프로세스일 경우 코드를 공유하기는 한다. | 같은 프로세스 내 스레드들은 스택을 제외한 나머지 세 영역을 공유한다. |
독립성 여부 | 일반적으로 독립적 | 일반적으로 프로세스의 하위 집합 |
주소 소유 여부 | 별개의 주소 공간을 갖는다 | 주소 공간을 공유한다. |
통신 여부 | 오직 시스템이 제공하는 IPC 방법으로만 통신 | 공유 변수 수정 등 자유롭게 다른 스레드와 소통 |
Context Switch | 일반적으로 프로세스보다 스레드의 Context Switching이 더 빠를 수 있다. | 하지만 상황에 따라 그렇지 않을 수도 있다. |
4. 자료 출처
- Wikipedia: Thread
- Wikipedia: Process
- https://brunch.co.kr/@kd4/3
- https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html
- https://magi82.github.io/process-thread/
- https://lalwr.blogspot.com/2016/02/process-thread.html
- http://wanzargen.tistory.com/27
- https://www.quora.com/How-does-thread-switching-differ-from-process-switching-What-is-the-performance-difference