컴퓨터에서 여러 작업을 동시에 처리할 때 우리는 ‘프로세스’와 ‘스레드’라는 용어를 자주 접합니다.
비슷해 보이지만 자원을 공유하는 방식에서 큰 차이가 있죠.
오늘은 이 둘의 차이점과 함께, 스레드를 쓸 때 반드시 마주하게 되는 락(Lock)의 딜레마에 대해 정리해 보았습니다.
프로세스와 스레드의 메모리 구조
프로세스와 스레드의 가장 큰 차이는 “무엇을 공유하느냐”에 있습니다.
이를 메모리 구조 관점에서 비교해 보면 다음과 같습니다.
[ Process 1 ] [ Process 2 ]
- Code / Data / Heap - Code / Data / Heap
- Stack (Thread A) - Stack (Thread C)
- Stack (Thread B)
먼저 프로세스는 독립된 메모리 공간을 가집니다.
서로 데이터를 주고받으려면 IPC(Inter-Process Communication)와 같은 복잡한 과정이 필요하고, 문맥 교환(Context Switching) 시 발생하는 오버헤드도 상대적으로 큽니다.
반면 스레드는 프로세스 내에서 힙(Heap) 영역을 공유하며 실행됩니다.
같은 프로세스 안에서 데이터를 주고받으므로 통신 속도가 매우 빠르고 가볍지만, 하나의 스레드에서 메모리 오염이 발생하면 프로세스 전체에 영향을 줄 수 있다는 위험도 안고 있습니다.
락(Lock)의 딜레마: 안전과 성능 사이
스레드가 자원을 공유한다는 것은 양날의 검과 같습니다.
여러 스레드가 동시에 같은 데이터를 수정하려고 하면 데이터가 꼬이는 ‘경쟁 상태(Race Condition)’가 발생하기 때문입니다.
이를 막기 위해 우리는 락(Lock)을 사용하여 데이터의 일관성을 보장합니다.
하지만 여기서 어려운 딜레마가 발생합니다.
락을 너무 많이 사용하면 데이터는 안전해지지만, 스레드들이 서로 순서를 기다리느라 성능이 급격히 떨어지게 됩니다.
심지어 서로가 서로의 락이 풀리기를 기다리는 데드락(Deadlock)에 빠질 수도 있습니다.
반대로 락을 너무 적게 쓰면 성능은 좋아지겠지만, 언제 어디서 데이터가 깨질지 모르는 불안한 프로그램이 되고 맙니다.
결국 멀티스레드 프로그래밍의 핵심은 자원 공유의 효율성을 극대화하면서도, 최소한의 락으로 데이터의 안정성을 보장하는 ‘설계의 묘’를 찾는 데 있습니다.
보이지 않는 곳에서 수많은 스레드가 얽히고설켜 돌아가는 현대 소프트웨어의 복잡성을 이해하는 데 큰 도움이 된 공부였습니는 것 같다.