멀티 스레딩 환경에서 데이터의 일관성과 정확성을 보장하기 위한 개념이다.
여러 스레드가 동시에 같은 데이터에 접근할 때 발생할 수 있는 문제를 방지
하기 위한 다양한 기법이 존재한다.
Thread Safe
멀티 스레드 프로그래밍 환경에서 함수나 변수, 객체에 여러 스레드가 동시에 접근이 발생해도 프로그램에 문제가 발생하지 않는 것을 의미한다.
즉, 하나의 스레드로부터 실행 중인 함수가 있을 때, 다른 스레드가 해당 함수를 사용하더라도 각 스레드에서 호출한 함수의 결과가 올바르게 출력되는 것을 의미한다.
Thread Safe를 위한 4가지 방법
- Mutual Exclustion (상호 배제)
- Atomic Operation (원자 연산)
- Thread-Local Stroage (스레드 지역 저장소)
- Re-Entrancy (재진입성)
1) Mutual Exclusion
뮤텍스 락 예시
pthread_mutex_lock(&mutx);
Critical Section (임계 구역) ...
pthread_mutex_unlock(&mutx);
공유 자원에 대해서 하나의 Thread만 접근할 수 있도록 Mutex / Semaphore를 사용하여 Critical Section에 여러 스레드가 진입하지 못하도록 한다.
2) Atomic Operation
원자적(Atomic) 연산(Operation)은 중간 단계 없이 한 번의 연산으로 완료되는 작업이다.
실행 중에 다른 스레드에 의해 중단될 수 없으며, 시작되면 반드시 완료된다.
즉, 다른 스레드가 진행 중인 작업의 부분 완료 상태를 볼 수 없다.
여러 스레드가 동일한 변수를 동시에 수정할 때, 원자 연산을 사용하면 해당 연산이 완벽하게 수행되거나 아예 수행되지 않은 것처럼 다른 스레드에 보이게 된다.
이는 중간에 값이 변경되는 것을 방지하여 데이터의 무결성을 보장한다.
비원자적 증가 연산
int count = 0;
void increment() {
counter = counter + 1;
}
counter = counter + 1 : 읽기, 증가, 쓰기 3번의 작업 수행
여러 스레드가 동시에 이 연산을 수행하면, 일부 증가가 누락될 수 있다.
ex. 두 스레드가 동시에 접근하여 초기값 0을 읽고 각각 1을 더한다.
원자적 증가 연산
AtomicInteger count = new AtomicInteger(0);
void increment() {
count.incrementAndGet();
}
- 현재 값을 읽는다.
- 값을 증가시킨다.
- 변경된 값을 다시 쓴다.
이 세 과정이 하나의 원자적 단위로 처리되기 때문에, 동시에 이 메서드를 호출하더라도 각 호출은 순차적으로 처리된다.
3) Thread-Local Storage
공유 자원의 사용을 최대한 줄이기 위해, 각 스레드에서만 접근이 가능한 저장소를 사용하여 동시 접근을 방지한다.
각 스레드가 데이터의 복사본을 가지고 작업을 진행하기 때문에 다른 스레드와의 데이터 공유를 하지 않는다.
이 방식을 사용하면 스레드 간에 데이터를 공유하지 않으므로 동기화에 대한 걱정 없이 데이터를 사용할 수 있다.
4) Re-Entrancy
함수가 여러 스레드에 의해 호출되더라도 각각의 호출이 독립적으로 안전하게 수행될 수 있도록 한다.
재진입 가능한 함수는 전역 변수나 정적 변수와 같은 공유 상태에 의존하지 않고, 로컬 변수와 매개변수만을 사용하여 상태를 관리한다.