Post

C++ - 쓰레드

C++ 쓰레드

1. 개요

C++에서 쓰레드(thread)란 하나의 프로세스 내에서 독립적으로 실행되는 흐름을 말한다. 쓰레드를 이용하면 CPU 자원을 효율적으로 활용하여 동시에 여러 작업을 수행할 수 있다. C++11부터는 헤더를 통해 표준 라이브러리 차원에서 쓰레드를 지원하며, 운영체제별 네이티브 쓰레드를 추상화하여 제공한다.

1) 장점

  • 병렬 처리로 연산 속도 향상
  • 입출력 대기 중에도 다른 작업 수행 가능

2) 단점

  • 동시성 제어를 잘못하면 경쟁 상태(race condition), 교착 상태(deadlock) 발생
  • 디버깅 난이도 상승

2. 기본적인 사용법

std::thread 클래스를 사용하여 쓰레드를 생성하고 실행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>

// 쓰레드에서 실행할 함수
void print_message(const std::string& msg, int count) {
    for (int i = 0; i < count; ++i) {
        std::cout << "Message: " << msg << " (" << i+1 << ")\n";
    }
}

int main() {
    // 1) 함수 포인터를 직접 전달
    std::thread t1(print_message, "Hello", 5);

    // 2) 멤버 함수 실행 (this 포인터 전달 필요)
    struct Worker {
        void do_work() { std::cout << "Worker doing work\n"; }
    } worker;
    std::thread t2(&Worker::do_work, &worker);

    
    t1.join();   // 메인 쓰레드는 t1이 끝날 때까지 대기
    t2.join();    // 메인 쓰레드는 t2이 끝날 때까지 대기

    std::cout << "All threads finished\n";
    return 0;
}

3. 동시성 제어 방법

여러 쓰레드가 공유 자원에 동시에 접근하면 데이터 경합(race condition)이 발생한다. 이를 제어하기 위해 아래와 같은 동시성 제어 메커니즘을 사용한다.

도구특징
std::mutex상호 배제(mutex). lock()/unlock()으로 임계 영역 보호
std::lock_guardRAII 스타일 락 관리. 생성 시 락, 소멸 시 언락 자동 수행
std::unique_locklock_guard보다 유연. 조건 변수와 함께 사용 가능
std::condition_variable쓰레드 간 시그널링. 특정 조건 만족 시 쓰레드 대기/알림
std::atomic<T>원자적 연산 지원. 간단한 카운터나 플래그에 유용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <atomic>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;
int shared_data = 0;
std::atomic<int> counter{0};

void producer() {
    {
        std::lock_guard<std::mutex> lk(mtx);
        shared_data = 42;
        ready = true;
        std::cout << "[Producer] 데이터 준비 완료\n";
    }
    cv.notify_one();  // consumer 깨우기
}

void consumer() {
    std::unique_lock<std::mutex> lk(mtx);
    cv.wait(lk, []{ return ready; });  // ready == true 될 때까지 대기
    std::cout << "[Consumer] 받은 데이터: " << shared_data << "\n";
}

void atomic_task() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    // condition_variable 예제
    std::thread prod(producer);
    std::thread cons(consumer);
    prod.join();
    cons.join();

    // atomic 예제: race condition 없이 카운팅
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i)
        threads.emplace_back(atomic_task);
    for (auto& t : threads)
        t.join();
    std::cout << "[Atomic] 최종 counter 값: " << counter.load() << "\n";

    return 0;
}
  • std::lock_guard<std::mutex> lk(mtx);
    스코프를 벗어나면 자동으로 mtx.unlock()

  • cv.wait(lk, predicate);
    predicate()가 true일 때까지 자동으로 unlock()/lock() 반복

  • std::atomic
    원자 연산으로 별도의 락 없이도 안전한 접근 보장

참고자료

This post is licensed under CC BY 4.0 by the author.