-
[OS] Thread의 구조부터 Race Condition까지: 동시성 정리CS/OS 2025. 11. 26. 15:58
현대 애플리케이션은 여러 작업을 동시에 수행한다. 웹 서버는 동시에 수십만 요청을 처리하고, 모바일 앱은 UI 스레드와 네트워크 스레드를 분리해 부드러운 경험을 제공한다.
이처럼 동시성(Concurrency)을 가능하게 하는 핵심 개념이 바로 Thread다.
이번 글에서는 Thread의 구조, 장점, 그리고 왜 동시성이 어렵게 느껴지는지 OS 관점에서 정리해보았다.
Thread란 무엇인가?
초기 운영체제에서 프로세스는 하나의 실행 흐름(PC)만 가진 단일 구조였다.
하지만 현대 OS에서는 프로세스가 여러 스레드를 포함하는 실행 단위가 되었다.스레드가 갖는 요소
- Program Counter(PC): 다음 실행할 명령어 위치
- Registers: 계산 작업에 필요한 레지스터 집합
- Stack: 함수 호출 스택(스레드별 독립)
스레드가 공유하는 영역
- Code
- Data
- Heap

다음 그림을 보면 더 명확하다.

왜 스레드를 사용하는가?
병렬성(Parallelism): 멀티코어의 시대
하나의 CPU에서는 변화를 못 느낄 수도 있지만, 멀티코어에서는 스레드 수만큼 병렬 실행이 가능하다.
따라서 CPU 바운드 작업은 스레드 기반 병렬화로 성능을 크게 개선할 수 있다.I/O 대기 감소
프로그램이 파일 읽기나 네트워크 요청처럼 느린 I/O를 기다릴 때,
- 싱글 스레드는 전체 프로그램이 멈추지만
- 멀티 스레드는 다른 스레드가 CPU를 계속 활용할 수 있다.
프로세스 기반 동시성도 가능하지만,
- 프로세스 간 주소 공간이 분리되어 있어 데이터 공유가 어렵고
- 생성 비용도 스레드보다 훨씬 크다.
따라서 스레드는 훨씬 가볍고 효율적이다.
스레드 스케줄링은 예측할 수 없다
A, B를 출력하는 두 스레드를 생성한다.
하지만 실행할 때마다 출력 순서가 매번 다르다.
왜냐하면 OS 스케줄러가 어떤 시점에 어떤 스레드를 실행할지 결정하기 때문이다.
이 에측 불가능성은 동시성 문제의 출발점이다.진짜 문제는 공유 데이터이다
i) 전역 변수 counter += 1을 여러 스레드가 수행하는 경우
예제에서는 두 스레드가 각자 10,000,000번 counter를 증가시키면 정답은 20,000,000이어야 한다.
하지만 실제 출력은 다음처럼 매번 다른 값이 나온다.
counter = 20000000 counter = 19345221 counter = 19221041왜 이런일이 발생할까?
원인: counter = counter + 1 은 ‘원자적’이지 않다
C 코드 1줄은 실제로는 아래 3개의 어셈블리 명령어로 분해된다.

이 사이에 스케줄러가 다른 스레드를 끼워넣을 수 있다.
Race Condition이 발생
두 스레드가 다음처럼 실행된다고 생각해보자:
- Thread 1: counter(50)을 레지스터에 로드
- Thread 2: counter(50)을 로드
- Thread 2: +1 → counter = 51 저장
- Thread 1: +1 → counter = 51 저장 (Thread2의 결과를 덮어버림)

분명히 두 번 증가해야 할 값이 단 한 번만 증가했다.
이렇게 실행 타이밍에 따라 결과가 달라지는 상황을 Race Condition이라고 한다.해결책: Critical Section + Mutual Exclusion
Critical Section
공유 데이터에 접근하는 코드 영역.
이 부분은 둘 이상의 스레드가 동시에 실행하면 안 된다.
Mutual Exclusion(상호 배제)
Critical Section에 한 번에 하나의 스레드만 들어가도록 보장하는 것.
여기서 등장하는 것이 Lock/Mutex/Semaphore 등의 동기화 메커니즘이다.
Atomicity(원자성)의 필요성
이상적인 세계라면, 이렇게 한 줄짜리 명령이 있으면 좋겠다.
memory-add address, 1 (완전 원자적임)하지만 현실의 CPU는 이런 고급 명령을 제공하지 않는다.
대신 다음과 같은 기본 원자적 primitive를 제공한다.- atomic add
- compare-and-swap(CAS)
- test-and-set
- lock prefix
운영체제와 프로그래밍 언어는 이러한 원자적 명령을 기반으로
락(mutex), 세마포어(semaphore), 조건 변수 등의 고수준 동기화를 구성한다.출처: 경북대학교 한명균 교수님, "운영체제" 강의 자료
'CS > OS' 카테고리의 다른 글
[OS] Lock 구현의 기본 원리 (0) 2025.11.28 [OS] 운영체제 메모리 관리: Free-Space Management (0) 2025.11.17 [OS] Page Replacement: FIFO, LRU, Clock, 그리고 Thrashing (0) 2025.10.24 [OS] Swap Space와 Page Fault: 메모리 부족을 해결하는 운영체제의 방식 (0) 2025.10.17 [OS] 페이지 테이블의 공간 효율화 단계: Multi-level과 Inverted Page Table (0) 2025.10.17