C++현대 C++

스레드와 동시성 기본

`std::thread`로 스레드를 생성하고, `mutex`와 `lock_guard`로 공유 데이터를 보호하는 C++ 동시성의 핵심 패턴을 정리합니다.

마지막 수정 2026년 3월 26일

기본 패턴

cpp
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    std::lock_guard<std::mutex> lock{mtx};  // 잠금 (범위 종료 시 자동 해제)
    ++counter;
}

int main() {
    std::thread t1{increment};
    std::thread t2{increment};
    t1.join();   // t1 완료 대기
    t2.join();   // t2 완료 대기
}

설명

스레드 생성과 join / detach

cpp
#include <thread>
#include <iostream>

void task(int id) {
    std::cout << "스레드 " << id << " 실행\n";
}

// 함수 포인터
std::thread t1{task, 1};

// lambda
std::thread t2{[](int id){ std::cout << id; }, 2};

// join — 스레드가 끝날 때까지 호출 스레드가 대기
t1.join();

// detach — 호출 스레드와 분리, 독립적으로 실행
// t2.detach();  // join 또는 detach 중 하나는 반드시 호출해야 함
t2.join();

mutex — 공유 데이터 보호

여러 스레드가 같은 데이터를 동시에 수정하면 데이터 경쟁(data race) 이 발생합니다.

cpp
#include <mutex>

std::mutex mtx;
int shared = 0;

void safeIncrement() {
    mtx.lock();
    ++shared;           // 한 번에 하나의 스레드만 진입
    mtx.unlock();
}

// ❌ lock/unlock 직접 호출 — 예외 발생 시 unlock 누락 위험

lock_guard / unique_lock — RAII 방식 잠금

cpp
// lock_guard — 간단한 잠금, 범위 벗어나면 자동 해제
void safe1() {
    std::lock_guard<std::mutex> lock{mtx};
    ++shared;
}   // 여기서 자동 unlock

// unique_lock — 잠금/해제를 수동 제어하거나 조건 변수와 함께 사용
void safe2() {
    std::unique_lock<std::mutex> lock{mtx};
    ++shared;
    lock.unlock();      // 명시적 해제 가능
    // 잠금 없이 다른 작업...
    lock.lock();        // 다시 잠금
}

조건 변수 — 스레드 간 신호

cpp
#include <condition_variable>
#include <queue>

std::mutex qmtx;
std::condition_variable cv;
std::queue<int> dataQueue;

// 생산자
void producer() {
    std::lock_guard<std::mutex> lock{qmtx};
    dataQueue.push(42);
    cv.notify_one();    // 대기 중인 스레드 하나 깨우기
}

// 소비자
void consumer() {
    std::unique_lock<std::mutex> lock{qmtx};
    cv.wait(lock, []{ return !dataQueue.empty(); });  // 조건 만족까지 대기
    int val = dataQueue.front();
    dataQueue.pop();
}

std::async / std::future — 결과값을 받는 비동기 작업

cpp
#include <future>

int heavyWork(int n) {
    return n * n;
}

// async로 비동기 실행 후 future로 결과 수신
std::future<int> result = std::async(std::launch::async, heavyWork, 10);

// 다른 작업 수행...

int value = result.get();   // 완료까지 대기 후 값 반환 (100)

빠른 정리

항목역할
std::thread스레드 생성
.join()스레드 완료까지 대기 (반드시 호출)
.detach()스레드를 독립 실행으로 분리
std::mutex상호 배제 잠금
std::lock_guardRAII 잠금 (범위 기반 자동 해제, 권장)
std::unique_lock수동 제어 가능한 잠금, 조건 변수와 함께 사용
std::condition_variable스레드 간 이벤트 신호
std::async비동기 함수 실행, 결과는 future
std::future::get()비동기 결과 수신 (완료까지 대기)

주의할 점

std::thread 객체가 소멸될 때 join()detach()도 호출하지 않으면 std::terminate()가 호출됩니다. 반드시 둘 중 하나를 호출하세요.

데이터 경쟁은 컴파일러 경고 없이 발생합니다. 공유 데이터에 접근하는 모든 경로에서 동일한 mutex로 보호해야 합니다. 락을 잡은 채로 다른 락을 잡으면 교착 상태(deadlock) 가 발생할 수 있습니다.

참고 링크

1 sources