[C++] Thread란?
Thread의 개념
스레드는 프로세스 내에서 실행되는 흐름의 단위입니다. 하나의 프로세스는 하나 이상의 스레드를 가질 수 있으며, 이 스레드들은 프로세스의 메모리 공간(코드, 데이터, 힙 영역 등)을 공유합니다. 이를 통해 여러 작업을 동시에 수행하는 것처럼 보이게 하는 병렬성(Concurrency)을 구현할 수 있습니다.
현업 특히 게임, GUI,네트워크등 여러 분야에서 많이 사용하기 때문에 Thread는 확실히 잡으시는것을 강력히
추천합니다.
Thread의 장단점
장점
- 응답성 향상: 긴 작업을 별도의 스레드에서 실행하면 메인 스레드가 멈추지 않고 사용자 인터페이스를 계속 처리할 수 있어 응답성이 향상됩니다.
- 성능 향상 (멀티 코어 환경): 멀티 코어 프로세서에서 여러 스레드를 동시에 실행하면 작업을 분산 처리하여 전체적인 실행 속도를 향상시킬 수 있습니다.
- 자원 공유의 용이성: 프로세스 내의 스레드들은 메모리를 공유하므로 데이터 교환이 용이합니다.
단점
- 동기화 문제 (경쟁 조건, 교착 상태): 여러 스레드가 공유 자원에 동시에 접근하려고 할 때 발생할 수 있는 문제점들을 해결하기 위해 동기화 기법 (뮤텍스, 세마포어, 조건 변수 등)을 사용해야 합니다.
- 경쟁 조건(Race Condition): 여러 스레드가 공유 자원에 동시에 접근하여 결과를 예측할 수 없게 되는 상황.
- 교착 상태(Deadlock): 두 개 이상의 스레드가 서로가 가진 자원을 기다리느라 무한정 멈춰있는 상황.
- 디버깅의 어려움: 스레드 관련 버그는 발생 빈도가 낮거나 재현하기 어려울 수 있어 디버깅이 어렵습니다.
- 컨텍스트 스위칭 오버헤드: 스레드를 전환하는 과정에서 오버헤드가 발생할 수 있습니다. 너무 많은 스레드를 생성하면 오히려 성능이 저하될 수 있습니다.
용도
주로 아래의 용도로 사용됩니다.
- GUI 프로그래밍: 사용자 인터페이스의 응답성을 유지하기 위해.
- 네트워크 프로그래밍: 여러 클라이언트의 연결을 동시에 처리하기 위해.
- 병렬 연산: 계산량이 많은 작업을 여러 스레드로 분할하여 처리하기 위해.
- 게임 프로그래밍: 게임 로직, 그래픽 렌더링, 사운드 재생 등을 별도의 스레드에서 처리하기 위해.
코드 및 설명
[간단한 예시]
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
std::mutex mtx; // 뮤텍스 (상호 배제)
void worker_thread(int id) {
for (int i = 0; i < 5; ++i) {
{ // 스코프 기반 락 (RAII)
std::lock_guard<std::mutex> lock(mtx); // 뮤텍스 획득
std::cout << "Thread " << id << ": Count " << i << std::endl;
} // 뮤텍스 자동 해제
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::thread t1(worker_thread, 1);
std::thread t2(worker_thread, 2);
std::cout << "Main thread continues..." << std::endl;
t1.join(); // 스레드 t1 종료 대기
t2.join(); // 스레드 t2 종료 대기
std::cout << "All threads finished." << std::endl;
return 0;
}
[잘못된 사용법]
- 데이터 경쟁(Data Race): 동기화 없이 여러 스레드가 동일한 메모리 위치에 동시에 접근하는 경우, 예측 불가능한 결과를 초래합니다.
반드시 뮤텍스 등의 동기화 기법을 사용해야 합니다.
- 교착 상태(Deadlock): 두 개 이상의 스레드가 서로가 가진 자원을 기다리는 상황.락의 순서를 일관되게 유지하거나, 타임아웃을 사용하는 등의 방법으로 방지해야 합니다.
- 과도한 스레드 생성: 너무 많은 스레드를 생성하면 컨텍스트 스위칭 오버헤드가 증가하여 오히려 성능이 저하될 수 있습니다. 스레드 풀을 사용하는 것이 좋습니다.
- 스레드 종료를 제대로 처리하지 않는 경우: 스레드가 작업을 마치기 전에 강제로 종료하면 문제가 발생할 수 있습니다. join()을 사용하여 스레드 종료를 기다리거나, 적절한 종료 메커니즘을 구현해야 합니다.
- 전역 변수 남용: 전역 변수는 여러 스레드에서 공유되므로 동기화 문제를 일으킬 가능성이 높습니다. 가능한 한 지역 변수를 사용하고, 필요한 경우에만 적절한 동기화 기법을 사용하여 전역 변수에 접근해야 합니다.