멀티스레딩
📋 문서 버전
이 문서는 2개의 버전이 있습니다. 현재 최신 버전을 보고 있습니다.
멀티스레
개요
멀티스레(Multithreading은 하나의 프로스 내에서 여러의 스레드)를 동시에 실행하여로그램의 성과 반응성을상시키는 병렬팅 기법입니다 각 스레드 독립적인 실행 흐을 가지며, 프로세스의모리 공간과원을 공유으로써 효율적인 공유와 통신 가능합니다. 멀스레딩은 현 소프트웨어 개발에서 매우 중요한 개념으로, 특히 다중 코어 프로세서 환경에서 성능 극대화를 위해 널리 사용됩니다.
멀티스레딩은 주로 CPU 사용률을 높이고, I/O 대기 시간을 줄이며, 사용자 인터페이스의 반응성을 개선하는 데 활용됩니다. 예를 들어, 웹 브라우저는 한 스레드에서 웹 페이지를 렌더링하고 다른 스레드에서 자바스크립트를 실행하거나 네트워 요청을 처리함으로써 전체적인 성능을 향상시킵.
스레드의 개념
스레드란?
스레드는 프로세스 내에서 실행되는 최소한의 작업 단위입니다. 하나의 프로세스는 하나 이상의 스레드를 포함할 수 있으며, 각 스레드는 독립적인 프로그램 카운터, 스택, 레지스터 상태를 가지지만, 코드, 데이터, 힙 메모리 등은 프로세스 내의 다른 스레드와 공유합니다.
스레드와 프로세스의 차이
| 구분 | 스레드 | 프로세스 |
|---|---|---|
| 자원 공유 | 같은 프로세스 내 스레드는 메모리, 파일 디스크립터 등을 공유 | 각 프로세스는 독립된 메모리 공간과 자원을 가짐 |
| 생성 비용 | 낮음 (스택만 새로 할당) | 높음 (전체 메모리 공간 복제 필요) |
| 통신 | 공유 메모리를 통해 직접 통신 가능 | IPC(Inter-Process Communication) 필요 |
| 격리성 | 낮음 (한 스레드의 오류가 전체 프로세스에 영향) | 높음 (프로세스 간 격리됨) |
멀티스레딩의 종류
1. 프로세스 기반 병렬 처리 vs 스레드 기반 병렬 처리
- 프로세스 기반: 각 작업이 독립된 프로세스로 실행되며, 메모리 격리가 강력하지만 통신 오버헤드가 큼.
- 스레드 기반: 같은 프로세스 내에서 여러 스레드가 실행되어 자원 공유가 용이하지만, 동기화 문제 발생 가능.
2. 사용자 스레드(User-level Thread)와 커널 스레드(Kernel-level Thread)
- 사용자 스레드: 운영체제 커널이 인식하지 못하는 스레드로, 사용자 공간에서 스케줄링됨. 빠르지만, 한 스레드가 블로킹되면 전체 프로세스가 멈출 수 있음.
- 커널 스레드: 커널이 직접 관리하는 스레드로, 블로킹 작업에도 다른 스레드가 실행될 수 있음. 성능은 다소 낮을 수 있으나 안정성이 높음.
대부분의 현대 운영체제는 하이브리드 모델(1:1 또는 M:N 매핑)을 사용하여 두 방식의 장점을 결합합니다.
멀티스레딩의 장점과 단점
장점
- 성능 향상: 다중 코어 CPU를 효율적으로 활용하여 작업을 병렬 처리.
- 반응성 향상: GUI 애플리케이션에서 백그라운드 작업(예: 파일 다운로드)을 별도 스레드에서 수행하면 UI가 멈추지 않음.
- 자원 공유 용이: 같은 프로세스 내 스레드는 메모리와 자원을 쉽게 공유.
- 경제성: 프로세스 생성보다 스레드 생성이 자원 소모가 적음.
단점
- 동기화: 공유 자원에 대한 동시 접근으로 인해 경쟁 조건(Race Condition) 발생 가능.
- 데드락(Deadlock): 두 개 이상의 스레드가 서로를 기다리며 영원히 대기 상태에 빠짐.
- 디버깅 난이도 증가: 비결정적 행동으로 인해 재현과 분석이 어려움.
- 스레드 안전성: 모든 함수나 객체가 멀티스레드 환경에서 안전하게 작동하지는 않음.
멀티스레딩 구현 기술
1. 동기화 메커니즘
공유 자원의 안전한 접근을 보장하기 위해 다음과 같은 기법이 사용됩니다:
- 뮤텍스(Mutex): 한 번에 하나의 스레드만 접근을 허용.
- 세마포어(Semaphore): 특정 개수의 스레드만 자원에 접근하도록 제한.
- 모니터(Monitor): 언어 수준에서 제공하는 동기화 구조 (예: Java의
synchronized블록). - 원자 연산(Atomic Operations): CPU가 제공하는 비분리 연산으로 경쟁 조건 방지.
2. 프로그래밍 언어 지원
- Java:
Thread클래스와Runnable인터페이스 제공.java.util.concurrent패키지로 고수준 동시성 유틸리티 제공. - C++: C++11부터
<thread>라이브러리 도입.std::thread,std::mutex등 사용 가능. - Python:
threading모듈 제공. 하지만 GIL(Global Interpreter Lock)로 인해 CPU 바운드 작업에서는 진정한 병렬성을 제공하지 못함. - Go: 경량 스레드인 고루틴(Goroutine)과
channel을 통해 간편한 동시성 프로그래밍 가능.
활용 사례
- 서버 애플리케이션: 웹 서버는 각 클라이언트 요청을 별도 스레드에서 처리하여 동시 접속을 지원.
- 게임 엔진: 렌더링, 물리 연산, AI 계산 등을 별도 스레드에서 수행.
- 데이터 처리 시스템: 대량의 데이터를 여러 스레드로 분할하여 병렬 처리 (예: MapReduce).
- 실시간 시스템: 타이머, 센서 데이터 수집 등 실시간 작업을 별도 스레드 처리.
참고 자료 및 관련 문서
- POSIX Threads (Pthreads)
- Java Concurrency Tutorial – Oracle
- Tanenbaum, A. S., & Bos, H. (2015). Modern Operating Systems. Pearson.
- Goetz, B. et al. (2006). Java Concurrency in Practice. Addison-Wesley.
멀티스레딩은 병렬 컴퓨팅의 핵심 기술로, 올바르게 사용하면 시스템 성능을 크게 향상시킬 수 있으나, 동기화 및 스레드 안전성 문제를 신중히 고려해야 합니다.
프로세스와 스레드 상세 비교
멀티스레딩의 핵심인 프로세스와 스레드의 차이를 자원 관리와 실행 관점에서 비교한 표입니다.
| 구분 | 프로세스 (Process) | 스레드 (Thread) |
|---|---|---|
| 정의 | 실행 중인 프로그램의 독립된 인스턴스 | 프로세스 내에서 실행되는 흐름의 단위 |
| 메모리 구조 | 독립된 Code, Data, Heap, Stack 영역 보유 | Stack만 독립적, Code/Data/Heap 영역 공유 |
| 자원 소유 | OS로부터 독립적인 주소 공간 할당받음 | 부모 프로세스의 자원을 공유하여 사용 |
| 생성/전환 비용 | 높음 (PCB 생성 및 메모리 할당 필요) | 낮음 (TCB 생성 및 스택 할당만 필요) |
| 통신 방식 | IPC (Pipe, Message Queue, Shared Memory) | 공유 메모리를 통한 직접 통신 |
| 영향 범위 | 한 프로세스 장애가 다른 프로세스에 영향 없음 | 한 스레드 장애(Crash)가 프로세스 전체에 영향 |
컨텍스트 스위칭과 오버헤드
컨텍스트 스위칭(Context Switching)이란 CPU가 한 프로세스나 스레드에서 다른 프로세스나 스레드로 전환하며 실행하는 과정을 말합니다.
- 동작 원리: 현재 실행 중인 스레드의 상태(레지스터 값, 프로그램 카운터, 스택 포인터 등)를 TCB(Thread Control Block)에 저장하고, 다음에 실행할 스레드의 상태를 TCB에서 복구하여 CPU에 로드합니다.
- 오버헤드: 이 과정에서 CPU 사이클이 소모되며, 캐시 메모리의 데이터가 무효화(Cache Flush)되어 성능 저하가 발생합니다.
- 스레싱(Thrashing): 너무 많은 스레드를 생성하면, 실제 작업 시간보다 컨텍스트 스위칭에 소모되는 시간이 더 많아져 시스템 전체의 처리량이 급격히 떨어지는 현상이 발생합니다.
하드웨어 관점의 제약 사항
멀티스레딩은 논리적 효율성을 제공하지만, 물리적 하드웨어 구조로 인해 다음과 같은 성능 저하 요인이 존재합니다.
- 캐시 일관성(Cache Coherency): 여러 코어가 동일한 메모리 주소의 데이터를 각자의 L1/L2 캐시에 복사해 사용할 때, 한 코어가 데이터를 수정하면 다른 코어의 캐시 데이터는 무효화됩니다. 이를 동기화하는 과정에서 버스 트래픽이 증가하고 성능이 저하됩니다.
- 거짓 공유(False Sharing): 서로 다른 스레드가 서로 다른 변수를 사용하더라도, 그 변수들이 하드웨어의 동일한 캐시 라인(Cache Line)에 묶여 있을 때 발생합니다. 한 스레드가 자신의 변수를 수정하면 같은 라인에 있는 다른 스레드의 변수까지 무효화되어 불필요한 캐시 갱신이 반복되는 현상입니다.
고성능 동기화 기법
전통적인 뮤텍스나 세마포어의 성능 한계를 극복하기 위한 고급 기법들입니다.
읽기-쓰기 잠금 (Read-Write Lock)
데이터의 수정보다 조회가 훨씬 빈번한 경우에 사용합니다. - Read Lock: 여러 스레드가 동시에 읽는 것을 허용합니다. - Write Lock: 쓰기 작업 시에는 독점적인 권한을 가져 읽기와 쓰기 모두를 차단합니다.
락 프리(Lock-free) 알고리즘
뮤텍스와 같은 잠금 장치를 사용하지 않고, CPU의 CAS(Compare-And-Swap) 원자 연산을 이용하여 동시성을 제어합니다. 이는 데드락을 방지하고 컨텍스트 스위칭 오버헤드를 제거합니다.
Java를 이용한 Lock-free 스택 예제:
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeStack<T> {
private AtomicReference<Node<T>> head = new AtomicReference<>();
private static class Node<T> {
T value;
Node<T> next;
Node(T value) { this.value = value; }
}
public void push(T value) {
Node<T> newNode = new Node<>(value);
Node<T> oldHead;
do {
oldHead = head.get();
newNode.next = oldHead;
} while (!head.compareAndSet(oldHead, newNode)); // CAS 연산
}
public T pop() {
Node<T> oldHead;
Node<T> newHead;
do {
oldHead = head.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead)); // CAS 연산
return oldHead.value;
}
}
현대적 동시성 모델
전통적인 OS 스레드(Platform Thread)는 생성 비용이 크고 메모리 점유율이 높아 수만 개의 동시 연결을 처리하는 데 한계가 있습니다. 이를 해결하기 위한 최신 모델들이 도입되었습니다.
비동기 I/O와 이벤트 루프
- 이벤트 루프(Event Loop): 단일 스레드가 요청을 받고, I/O 작업은 커널에 위임한 뒤 콜백 함수를 통해 결과를 처리하는 방식입니다. (예: Node.js, Redis)
가상 스레드 (Virtual Threads / Project Loom)
Java 21 등 최신 런타임에서 도입된 가상 스레드는 OS 스레드와 1:1 매핑되지 않고, 수많은 가상 스레드가 소수의 플랫폼 스레드 위에서 스케줄링되는 M:N 모델을 사용합니다.
가상 스레드 동작 방식 다이어그램:
graph TD
subgraph "User Space (JVM)"
VT1[Virtual Thread 1] --> Carrier[Carrier Thread / Platform Thread]
VT2[Virtual Thread 2] --> Carrier
VT3[Virtual Thread 3] --> Carrier
VT4[Virtual Thread 4] --> Carrier
end
subgraph "OS Kernel"
Carrier --> OS_Thread[OS Kernel Thread]
end
note[I/O 블로킹 발생 시 가상 스레드는 <br/>Carrier에서 분리되어 대기하고 <br/>다른 가상 스레드가 Carrier를 점유함]
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.