본문으로 건너뛰기
모던 C++ 심화LESSON 22

멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유

난이도중급 → 심화
예상 시간70분
선수지식이전 강의

22강. 멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유

이번 강의에서 해결할 문제​

completedCount++는 한 줄이지만 읽기, 증가, 쓰기의 여러 단계입니다. 두 스레드가 동기화 없이 같은 객체에 접근하고 하나 이상이 쓰면 데이터 레이스가 되어 프로그램 전체가 UB입니다. 스레드를 추가하는 것만으로 안전하거나 빠르지 않습니다.

학습 목표​

먼저 알아야 할 핵심 개념​

데이터 레이스는 서로 동기화되지 않은 두 실행이 같은 메모리에 접근하고 적어도 하나가 쓰는 조건에서 발생합니다. mutex는 한 시점에 한 스레드만 보호 구역을 실행하게 합니다. lock_guard는 RAII로 unlock 누락을 막습니다. 스레드 객체가 파괴되기 전에 join 또는 명확한 소유 정책이 필요합니다.

왜 이 문제가 위험한가​

데이터 레이스 결과는 단순히 숫자가 조금 틀리는 문제가 아니라 UB입니다. mutex를 여러 순서로 잠그면 교착될 수 있고, 잠금 범위가 크면 병렬 이득이 사라집니다. 지역 변수 참조를 스레드에 넘긴 뒤 먼저 스코프를 벗어나도 댕글링이 됩니다.

가장 작은 예제로 시작하기​

parallel_count.cpp
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

int countCompleted(const std::vector<bool>& values) {
int count{};
for (const bool value : values) if (value) ++count;
return count;
}

int main() {
const std::vector<bool> left{true, false, true};
const std::vector<bool> right{true, true, false};
int total{};
std::mutex totalMutex;

auto worker = [&](const std::vector<bool>& values) {
const int local = countCompleted(values);
std::lock_guard<std::mutex> lock{totalMutex};
total += local;
};

std::thread first{worker, std::cref(left)};
std::thread second{worker, std::cref(right)};
first.join();
second.join();
std::cout << total << '\n';
}

코드 한 줄씩 이해하기​

  1. 각 스레드는 읽기 전용 입력으로 지역 합계를 계산해 공유 쓰기를 줄입니다.
  2. 짧은 total += local만 mutex로 보호합니다.
  3. lock_guard가 스코프 종료 시 mutex를 자동으로 해제합니다.
  4. std::cref는 vector를 복사하지 않지만 수명을 늘리지 않으므로 join까지 두 vector가 살아 있어야 합니다.
  5. 두 join 뒤에만 최종 total을 읽습니다.

실행 결과와 메모리·동작 흐름​

각 작업이 2를 계산하고 mutex 아래에서 합쳐 최종 4가 출력됩니다. 실행 순서는 달라도 최종 결과는 같습니다. 잠금 밖 지역 계산에는 공유 쓰기가 없습니다.

단계별 실습​

흔한 실수와 해결 방법​

언제 사용하고 언제 피할까​

독립적이고 충분히 큰 작업을 나눌 수 있으며 측정된 병목이 있을 때 동시성을 고려합니다. 작은 작업, 공유 상태가 많은 로직, 순서가 핵심인 상태 변경에는 단일 스레드가 단순하고 충분할 수 있습니다.

게임 개발 연결​

Unreal의 Task Graph나 비동기 API를 쓰더라도 UObject와 대부분의 게임 상태를 아무 스레드에서나 접근할 수 있다고 가정하지 않습니다. 무거운 순수 계산은 독립 값으로 분리하고, 엔진 객체 반영은 요구되는 게임 스레드 경계로 돌려보냅니다. 표준 thread를 직접 만드는 것보다 엔진 작업 시스템의 수명·종료 규칙을 먼저 확인합니다.

이해 점검 질문 3개​

  1. total++가 한 줄이어도 데이터 레이스가 될 수 있는 이유는 무엇인가요?
  2. 지역 집계 뒤 짧게 잠그는 방식이 전체 루프 잠금보다 나은 이유는 무엇인가요?
  3. std::cref로 넘긴 입력이 join까지 살아 있어야 하는 이유는 무엇인가요?

핵심 요약​

다음 강의 연결​

23강. 디버깅과 코드 품질에서 컴파일러 경고, Assert와 Sanitizer로 수명·경쟁 버그의 근거를 찾습니다.

MINI QUIZ

멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유 미니 퀴즈

선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.

0 / 2
  1. 문제 1“멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유” 실습을 안전하게 진행하기 위한 판단으로 가장 알맞은 것은 무엇인가요?
  2. 문제 2“멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유”의 작업 기준으로 ‘detach로 수명 문제 숨김’을 진단하거나 바로잡은 선택은 무엇인가요?
LESSON STATUS

학습을 마쳤나요?

직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.

22강. 멀티스레드 입문: std::thread, 경쟁 상태, mutex를 조심해야 하는 이유 미완료 상태