현대 CPU는 대부분 멀티코어 환경입니다. 프로그램의 성능을 한계까지 끌어올리기 위해서는 작업을 병렬로 처리하는 멀티스레드(Multithreading) 프로그래밍이 필수입니다.
오늘은 C++11부터 표준으로 도입된 <thread> 라이브러리를 중심으로 멀티스레드의 기초부터 동기화 기법까지 핵심 예제와 함께 정리해 보겠습니다.

1. 멀티스레드 프로그래밍이란?
멀티스레딩은 하나의 프로세스 안에서 여러 개의 실행 흐름(스레드)을 만드는 것입니다. 스레드들은 프로세스의 힙(Heap) 영역과 데이터 영역을 공유하므로 스택만 독립적으로 가집니다. 덕분에 데이터 교환이 매우 빠르지만, 여러 스레드가 동시에 같은 데이터를 수정할 때 경합 조건(Race Condition)이 발생할 수 있습니다.
2. 기본 스레드 생성과 관리 (thread)
C++에서 스레드를 생성하는 것은 매우 직관적입니다. 하지만 생성된 스레드를 어떻게 종료 대기(join)하거나 분리(detach)할지 결정하는 것이 중요합니다.
#include <iostream>
#include <thread>
#include <string>
void worker(std::string name) {
std::cout << name << " 스레드가 작업을 시작합니다.\n";
}
int main() {
// 스레드 생성 및 함수 실행
std::thread t1(worker, "Thread A");
std::thread t2(worker, "Thread B");
// t1, t2 스레드가 종료될 때까지 메인 스레드가 기다립니다.
if (t1.joinable()) t1.join();
if (t2.joinable()) t2.join();
std::cout << "모든 작업 완료.\n";
return 0;
}
3. 뮤텍스(Mutex)를 이용한 데이터 보호
여러 스레드가 하나의 변수에 동시에 접근하면 데이터가 꼬이게 됩니다. 이를 방지하기 위해 한 번에 하나의 스레드만 접근할 수 있도록 문을 걸어잠그는 것이 뮤텍스(Mutual Exclusion)입니다.
lock_guard: 안전한 뮤텍스 관리
직접 lock()과 unlock()을 호출하면 예외 발생 시 잠금이 해제되지 않는 위험이 있습니다. C++에서는 RAII 패턴을 활용한 std::lock_guard 사용을 강력히 권장합니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx;
int shared_counter = 0;
void increase() {
for (int i = 0; i < 10000; ++i) {
// 중괄호를 나갈 때 자동으로 unlock 됩니다.
std::lock_guard<std::mutex> lock(mtx);
shared_counter++;
}
}
int main() {
std::thread t1(increase);
std::thread t2(increase);
t1.join();
t2.join();
std::cout << "최종 결과: " << shared_counter << std::endl;
return 0;
}
4. 조건 변수(Condition Variable)와 스레드 간 통신
스레드가 무작정 기다리는 것이 아니라, 특정 조건이 만족되었을 때 신호(notify)를 받고 깨어나도록 설계하면 CPU 자원을 훨씬 효율적으로 쓸 수 있습니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool is_ready = false;
void print_data() {
std::unique_lock<std::mutex> lock(mtx);
// 조건이 false이면 스레드를 대기 상태로 전환하고 mtx를 반납합니다.
cv.wait(lock, [] { return is_ready; });
std::cout << "데이터 처리 시작!\n";
}
int main() {
std::thread t(print_data);
{
std::lock_guard<std::mutex> lock(mtx);
is_ready = true;
}
cv.notify_one(); // 대기 중인 스레드 하나를 깨웁니다.
t.join();
return 0;
}
5. 멀티스레드 프로그래밍 시 주의사항 (실무 팁)
전문 블로그로서 신뢰도를 높여주는 핵심 가이드입니다.
- 데드락(Deadlock) 주의: 두 스레드가 서로 상대방이 가진 뮤텍스가 풀리기를 무한히 기다리는 상황을 주의하세요. 뮤텍스 잠금 순서를 일정하게 유지해야 합니다.
- 과도한 스레드 생성 지양: 스레드가 CPU 코어 수보다 너무 많아지면 컨텍스트 스위칭(Context Switching) 비용 때문에 오히려 성능이 떨어집니다.
- Atomic 활용: 단순한 증감 연산의 경우 뮤텍스보다 std::atomic을 사용하는 것이 성능상 훨씬 유리합니다.
- 디버깅의 난해함: 멀티스레드 버그는 재현이 어렵습니다(Heisenbug). 설계 단계에서 자원 공유를 최소화하는 것이 상책입니다.
결론
C++ 멀티스레딩은 성능 향상을 위한 강력한 도구이지만, 데이터 무결성을 지키기 위한 세심한 설계가 동반되어야 합니다. 오늘 배운 mutex와 condition_variable을 활용해 안전하고 빠른 병렬 처리 프로그램을 작성해 보세요!
궁금한 점이나 본인만의 멀티스레드 팁이 있다면 댓글로 공유해 주세요.
'Core Programming > Modern C++ & System Design' 카테고리의 다른 글
| C++ 파일 입출력 완벽 가이드: 바이너리 파일 읽기/쓰기 및 포인터 제어 (0) | 2024.12.21 |
|---|---|
| C++ 예외 처리 완벽 가이드: try-catch-throw 문법과 실전 예제 (0) | 2024.12.20 |
| C++ STL 총정리: 컨테이너, 반복자, 알고리즘 핵심 요약 및 예제 (0) | 2024.12.20 |
| C++ 템플릿(Template) 완벽 정리: 함수·클래스 템플릿부터 특수화까지 (0) | 2024.12.20 |
| C++ 네임스페이스(Namespace) 사용법 총정리: 이름 충돌 방지와 모듈화 (0) | 2024.12.20 |