반응형

std::thread 사용법 정리 (C++17 기준)

1. 기본 개념

  • C++11부터 도입된 std::thread는 병렬 실행을 위한 클래스입니다.
  • 쓰레드를 생성하면 자동으로 시작되며, 작업이 끝날 때까지 join() 또는 detach() 호출로 관리해야 합니다.

2. 기본 예제

#include <iostream>
#include <thread>

void say_hello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(say_hello); // 쓰레드 시작
    t.join();                 // 메인 쓰레드가 t를 기다림
}
  • join()이 없으면 프로그램이 끝나기 전에 쓰레드가 종료되지 않을 수 있어 런타임 오류 발생.

3. 인자 전달

void print_sum(int a, int b) {
    std::cout << a + b << std::endl;
}

int main() {
    std::thread t(print_sum, 3, 4);
    t.join();
}
  • 값은 복사되어 전달됩니다.
  • 참조를 넘기려면 std::ref() 사용
void modify(int& x) {
    x += 10;
}

int main() {
    int value = 5;
    std::thread t(modify, std::ref(value));
    t.join();
    std::cout << value << std::endl; // 15
}

4. 람다 함수 사용

std::thread t([](){
    std::cout << "Running in a lambda thread!" << std::endl;
});
t.join();

5. 멤버 함수 호출

class Worker {
public:
    void run() {
        std::cout << "Worker running!" << std::endl;
    }
};

int main() {
    Worker w;
    std::thread t(&Worker::run, &w); // 객체 포인터 전달
    t.join();
}

6. join() vs detach()

  • join(): 쓰레드가 끝날 때까지 기다림
  • detach(): 쓰레드를 백그라운드에서 실행시키고 제어를 넘김
std::thread t([]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Detached thread done" << std::endl;
});
t.detach(); // join하지 않아도 됨

주의: detach한 쓰레드는 더 이상 제어할 수 없으며, 메인 함수가 먼저 종료되면 문제가 생길 수 있음.


7. 쓰레드 ID 확인

 
std::cout << std::this_thread::get_id() << std::endl;

8. 쓰레드 관련 함수들

  • std::this_thread::sleep_for(duration): 일정 시간 동안 sleep
  • std::this_thread::yield(): 다른 쓰레드에게 CPU 양보
  • std::thread::hardware_concurrency(): 시스템의 논리 코어 수 반환

9. 예외 처리

std::thread 자체는 예외를 전달하지 않기 때문에, 예외는 쓰레드 내부에서 처리해야 합니다.

std::thread t([]() {
    try {
        throw std::runtime_error("error!");
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
});
t.join();

10. C++17에서 유용한 추가 기능

  • std::scoped_lock을 쓰레드와 뮤텍스에 함께 사용 가능
  • std::shared_mutex (읽기/쓰기 락)과 함께 사용 시 성능 최적화 가능
  • 쓰레드 안전한 std::atomic 변수와 병행 사용

 

std::shared_mutex란?

  • 동시에 여러 개의 쓰레드가 읽을 수 있도록 허용하지만,
  • 쓰기(수정) 시에는 단 하나의 쓰레드만 접근 가능하게 만드는 락입니다.

읽기: 병렬 허용
쓰기: 단독만 허용

예: std::mutex와 std::shared_mutex의 차이

일반 std::mutex 사용 시

cpp
std::mutex mtx; void read() {
std::lock_guard<std::mutex> lock(mtx); // 읽기 작업
}

읽기조차 직렬화됨 (읽기끼리도 동시에 접근 불가)


std::shared_mutex 사용 시

#include <shared_mutex>

std::shared_mutex shared_mtx;

void read() {
    std::shared_lock<std::shared_mutex> lock(shared_mtx); // 여러 쓰레드 동시에 가능
    // 읽기 작업
}

void write() {
    std::unique_lock<std::shared_mutex> lock(shared_mtx); // 단독 접근
    // 쓰기 작업
}

여러 쓰레드가 동시에 읽기 가능
쓰기 쓰레드는 단독 접근만 허용 (읽기 중인 쓰레드가 끝날 때까지 대기)

성능 최적화의 이유

쓰기보다 읽기가 많은 상황 (예: 캐시, 설정 조회 등)에서는:

  • std::mutex: 모든 작업이 직렬 → 비효율적
  • std::shared_mutex: 읽기 병렬화 가능 → 성능 향상
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>

std::shared_mutex shared_mtx;
int shared_data = 0;

void reader(int id) {
    std::shared_lock<std::shared_mutex> lock(shared_mtx);
    std::cout << "Reader " << id << " read value: " << shared_data << std::endl;
}

void writer(int value) {
    std::unique_lock<std::shared_mutex> lock(shared_mtx);
    shared_data = value;
    std::cout << "Writer wrote value: " << value << std::endl;
}

int main() {
    std::vector<std::thread> threads;

    // 읽기 쓰레드 여러 개
    for (int i = 0; i < 5; ++i)
        threads.emplace_back(reader, i);

    // 쓰기 쓰레드 하나
    threads.emplace_back(writer, 100);

    for (auto& t : threads) t.join();
}

'Develop > C&CPP' 카테고리의 다른 글

[cpp] optional  (0) 2025.04.25
[cpp] atomic  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20

+ Recent posts