프로세스와 스레드 (Process and Thread)
"Parallel execution, shared memory, maximum throughput"
— 멀티코어 CPU 자원을 끝까지 짜내어 연산 효율을 극대화하자는 현대 동시성 프로그래밍의 정수.
이론상 동시에 여러 일을 처리해 주는 훌륭한 아키텍처지만, 조금만 설계를 잘못하면 1초 뒤에 데이터가 엉망으로 꼬이고 메모리가 터져 나가는 일촉즉발의 화약고. 결국 멀티스레드가 무서워 싱글스레드 기반의 자바스크립트나 비동기 코루틴으로 피신하는 개발자들의 심정이 백번 이해된다.(...)
1. 개요
프로세스와 스레드는 운영체제(OS)가 실행 중인 프로그램을 관리하는 두 가지 핵심 실행 단위를 일컫는다. 하드디스크에 잠자고 있는 프로그램을 더블 클릭하는 순간 운영체제는 독립된 독방(메모리 주소 공간)을 통째로 떼어주며 프로세스(Process)로 격상시킨다. 그리고 이 프로세스 방 안에서 실제로 주인의 심부름을 들고 뜀박질하는 일꾼들이 바로 스레드(Thread)이다. 방 안의 일꾼들은 서로 같은 서랍(공유 메모리)을 공유하여 긴밀히 협력하지만, 이 때문에 엄청난 골칫거리들을 양산하기도 한다.(...)
2. 독립된 독방(프로세스) vs 주방 공유(스레드)
프로세스와 스레드의 본질적인 차이는 자원을 나누어 쓰는 방식에 있다.
2.1. 프로세스 (Process): 완벽한 고립
- 하나의 프로그램 실행 단위로, 운영체제로부터 코드(Code), 데이터(Data), 스택(Stack), 힙(Heap) 영역이 완전히 고립된 독립 메모리를 할당받는다.
- 한 프로세스가 오류로 튕겨서 사망(Crash)하더라도 옆방 프로세스에는 영향을 미치지 않는다. 예컨대 크롬 브라우저 탭 하나가 깨져도 다른 탭들이 멀쩡히 살아있는 이유가 바로 각 탭을 별도 프로세스로 띄웠기 때문이다.
- 서로 데이터를 주고받으려면 파이프나 소켓 같은 IPC(Inter-Process Communication) 기법을 써야 해서 통신 비용이 비싸다.
2.2. 스레드 (Thread): 한 지붕 아래 패밀리
- 하나의 프로세스 안에서 동작하는 여러 실행 흐름이다. 프로세스가 받은 밥그릇 중에서 코드, 데이터, 힙 영역을 모두 공유하고, 오직 자기만의 고유한 명령 주소록(스택 영역)만 독립적으로 쥔다.
- 힙 영역을 공유하므로 전역 변수를 통해 매우 빠르고 가볍게 소통할 수 있다.
- 하지만 영희 스레드가 밥을 먹는 도중 철수 스레드가 반찬을 채 가버리는 비극(동시성 문제)이 언제든 벌어질 수 있으며, 한 스레드가 뇌절하여 예외를 던지고 급사하면 그 프로세스 안의 모든 스레드가 연쇄 사망하는 한 배를 탄 운명이다.1
3. 동시성 제어와 교착 상태(Deadlock)
스레드들이 자원을 서로 가지겠다고 투쟁하는 과정에서 자원 경쟁은 물론, 시스템 전체가 얼어붙는 교착 상태가 자주 발생한다.
3.1. 레이스 컨디션 (Race Condition)
- 두 개 이상의 스레드가 하나의 공유 자원에 동시에 접근해 쓰기 연산을 할 때, 최종 연산 결과가 스레드 실행 순서에 따라 제멋대로 달라지는 현상이다. 이를 막기 위해 자원에 빗장을 걸어 잠그는 동기화(Synchronization - Mutex, Semaphore)를 동원해야 한다.
3.2. 데드락 (Deadlock: 교착 상태)
- 영희 스레드는 A 자원을 쥔 채 B 자원을 기다리고, 철수 스레드는 B 자원을 쥔 채 A 자원을 기다리는 기막힌 상태이다. 서로 양보할 생각 없이 무한 대기에 빠지는데, 이를 데드락이라 부르며 컴퓨터가 켜져 있음에도 아무 반응이 없는 프리징 현상을 자아낸다.2
4. 관련 밈 및 드립
4.1. 멀티스레딩은 하하하하하하하하하하
인터넷에서 멀티스레딩의 실체를 표현한 대표적인 타이포그래피 드립이다. 싱글스레드로 출력하면 순서대로 단정하게 글자가 나오지만, 멀티스레드로 동기화 없이 마구잡이 출력을 때리면 글자들이 사정없이 꼬여서 깨져버리는 현상을 조롱하는 이미지이다. 실무에서도 로그 출력이 꼬여서 I am aa ddeevveellooppeerr처럼 도배되는 현상이 심심치 않게 일어난다.
5. 여담
- 콘텍스트 스위칭(Context Switching)의 역설: 멀티코어가 아닐 때, 싱글코어 CPU는 한 번에 한 가지 일만 할 수 있다. 하지만 프로세스와 스레드를 아주 잘게 쪼개어 번갈아 실행(Context Switching)하기 때문에 사람이 보기엔 동시에 돌아가는 것처럼 느껴진다. 문제는 이 일꾼 교체 비용이 너무 비싸서, 스레드를 너무 많이 만들면 실제 일하는 시간보다 교체 전술 짜는 시간이 더 길어져 컴퓨터가 사르르 버벅대기 시작한다.
- 자바스크립트의 생존 전략: 자바스크립트는 멀티스레딩의 지옥 같은 동기화 버그를 원천 봉쇄하기 위해 애초에 싱글스레드(Single Thread) 언어로 설계되었다. 대신 틈만 나면 비동기(Event Loop, Callback Queue)로 연산을 위임하여 싱글스레드로도 준수한 동시성 성능을 이끌어내는 영리함을 선택했다.
- 상남자 식 해결책, 재부팅: 데드락이 깊게 걸려서 백엔드 서버의 수백 개 스레드가 전부 벽을 보며 명상에 빠졌을 때, 로그 분석을 하며 실타래를 푸는 것보다 그냥 도커 컨테이너를 재시작하거나 물리 장비를 껐다 켜는 것이 실무에서 가장 빠르게 서비스를 심폐소생술 하는 정석적인 비공식 치트키로 통한다.