
핵심요약
C++ 멀티 스레드 환경에서 데이터 레이스의 개념을 정확히 이해하는 것은 동시성 문제를 방지하고 스레드 안전한 코드를 작성하는 데 필수적입니다. 이 글은 C++의 동시성 문제와 스레드 안전성을 이론 및 실제 사례를 통해 설명합니다.
C++ 멀티 스레드 환경에서의 스레드 안전성 및 데이터 레이스 이해
- C++ 멀티 스레드 환경에서
mutex나atomic같은 동기화 도구를 사용해도 발생할 수 있는 동시성 문제와 디버깅의 어려움을 다룹니다. - 데이터 레이스 개념의 정확한 이해를 통해 동기화 도구가 해결하는 문제와 발생 상황을 파악합니다.
- 이 글은 C++ 동시성 문제와 이를 방지하기 위한 스레드 안전성의 주요 개념을 이론과 사례를 통해 설명합니다.
데이터 레이스 및 연산 간 선후 관계
- 데이터 레이스 정의: 두 개 이상 스레드가 동일 메모리 위치에 동시 접근하고, 하나 이상이 쓰기 연산을 수행하며, 하나 이상이 atomic 연산이 아닐 때 발생하며 **미정의 행동(undefined behavior)**으로 간주됩니다.
- 순차 실행 관계(sequenced-before): 같은 스레드 내에서 두 연산 사이에 명확한 순서가 존재함을 의미하며, 연산 간 선후 관계의 전제 조건입니다.
- 스레드 간 동기화 관계(synchronized-with):
std::atomic의store(memory_order::release)와load(memory_order::acquire),std::mutex의unlock()과lock()같은 특정 연산 쌍에서 성립하며 연산 간 선후 관계로 확장됩니다. - 동기화 관계는 멀티 스레드 환경에서 안전한 실행 순서를 정의하는 핵심 메커니즘으로, 데이터 레이스를 방지하는 데 필수적입니다.
**기본 스레드 안전성(Basic Thread Safety)**과 표준 라이브러리
- 기본 스레드 안전성 원칙: 사용자 정의 타입에서 5가지 상황(특히 '같은 변수 읽기/읽기', '다른 변수 쓰기/읽기', '다른 변수 쓰기/쓰기')에서 데이터 레이스가 발생하지 않아야 한다는 조건입니다.
- C++ 표준 라이브러리의 모든 타입은 기본 스레드 안전성을 보장하며, 컨테이너 타입은 포함하는 타입
T또한 기본 스레드 안전성을 만족해야 합니다. std::shared_ptr<T>는 서로 다른 객체 간 사용 시 안전하지만, 동일한shared_ptr객체에 대해 여러 스레드가 동기화 없이non-const멤버 함수를 호출하면 데이터 레이스가 발생합니다.mutable멤버 변수를 포함한const멤버 함수 내non-const동작이나 내부 자원을 공유하는shared_ptr패턴은 기본 스레드 안전성을 위반하는 흔한 사례입니다.
외부 동기화(External Synchronization) 및 동기화 기본 요소
- 외부 동기화 필요성: 기본 스레드 안전성만 보장하는 타입의 동일 객체에 대한
non-const함수 동시 호출 시, 사용자가std::mutex나std::atomic을 활용하여 연산 간 선후 관계를 명확히 해야 합니다. std::mutex::unlock()과lock()은 C++ 메모리 모델에서 동기화 관계를 형성하여 크리티컬 섹션 간의 실행 순서를 보장하고 데이터 레이스를 방지합니다.std::atomic은memory_order::release와memory_order::acquire를 통해 제3의atomic변수를 통한 간접적인 동기화를 제공하여 다른 데이터의 쓰기-읽기 순서를 보장할 수 있습니다.memory_order::relaxed는 동기화 관계를 형성하지 않으므로, 이를 무분별하게 사용할 경우 데이터 레이스 또는 의도치 않은 결과로 이어질 수 있어 신중한 사용이 요구됩니다.
내부 동기화 타입(Internally Synchronized Type) 구현 원리
- 내부 동기화 타입은 객체 사용자가 별도의 동기화 없이
non-const멤버 함수를 동시 호출해도 안전하도록 자체적으로 동기화를 수행하는 타입입니다. - 기본 스레드 안전성만 보장하는
bool이나int같은 타입만으로는 내부 동기화 타입을 구현할 수 없으며, 반드시std::atomic,std::mutex,std::condition_variable과 같은 동기화 기본 요소가 필요합니다. - 동기화 기본 요소는 그 자체로 내부 동기화 타입이며, 더 복잡한 스레드 안전 타입(
thread_safe_queue등)을 계층적으로 구축하는 데 사용됩니다. std::mutex는std::atomic을 사용하여 구현될 수 있듯이, 동기화 기본 요소들은 서로를 이용하여 구현 가능하며 논리적으로 동등한 역할을 수행합니다.