핵심 정리
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::atomic — 락 없는 원자적 연산
mutex 없이 단순 정수 연산을 안전하게 할 때 std::atomic을 씁니다. 하드웨어 원자 명령어를 사용해 락보다 빠릅니다.
cpp
#include <atomic>
std::atomic<int> counter{0};
void increment() {
++counter; // 원자적 증가 — 데이터 경쟁 없음
counter.fetch_add(1); // 동일, 이전 값 반환
counter.fetch_sub(1); // 원자적 감소
// CAS (Compare-And-Swap) — lock-free 알고리즘의 핵심
int expected = 5;
bool ok = counter.compare_exchange_strong(expected, 10);
// counter == expected(5)이면 10으로 교환, ok = true
// 다르면 expected = counter 현재값, ok = false
}thread_local — 스레드별 독립 저장소
cpp
// thread_local: 스레드마다 독립적인 인스턴스
thread_local int perThreadCounter = 0;
void worker() {
++perThreadCounter; // 각 스레드의 자신만의 카운터
std::cout << "스레드 카운터: " << perThreadCounter << "\n";
}
// t1과 t2는 각자의 perThreadCounter를 가짐 — 공유 없음
std::thread t1{worker};
std::thread t2{worker};
t1.join(); t2.join();
// 두 스레드 모두 "스레드 카운터: 1" 출력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_guard | RAII 잠금 (범위 기반 자동 해제, 권장) |
std::unique_lock | 수동 제어 가능한 잠금, 조건 변수와 함께 사용 |
std::condition_variable | 스레드 간 이벤트 신호 |
std::atomic<T> | 락 없는 원자 연산, 단순 카운터/플래그에 |
compare_exchange_strong | CAS — lock-free 알고리즘 기본 연산 |
thread_local | 스레드별 독립 변수 (공유 없음) |
std::async | 비동기 함수 실행, 결과는 future로 |
std::future::get() | 비동기 결과 수신 (완료까지 대기) |
주의할 점
std::thread 객체가 소멸될 때 join()도 detach()도 호출하지 않으면 std::terminate()가 호출됩니다. 반드시 둘 중 하나를 호출하세요.
데이터 경쟁은 컴파일러 경고 없이 발생합니다. 공유 데이터에 접근하는 모든 경로에서 동일한 mutex로 보호해야 합니다. 락을 잡은 채로 다른 락을 잡으면 교착 상태(deadlock) 가 발생할 수 있습니다.
참고 링크
1 sources