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_guard | RAII 스타일 락 관리. 생성 시 락, 소멸 시 언락 자동 수행 |
std::unique_lock | lock_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.