C++과 친해지기

Spider-Man: Into the Spider-Verse - 멀티스레딩

NAEU 2026. 1. 7. 22:08

오늘은 멀티스레딩에 대해 알아보자

좀 더 정확히 표현하자면 C++에서 어떻게 다루는지 알아보자

대충 알겠지만 멀티스레딩은 싱글스레딩만 해오던 나에게 굉장히 어려웠다

그렇기에 개념을 잡는 시간이 걸렸으며 사실 다 잡힌건 아닌 것 같다

따라서 앞으로도 개념정리는 계속 바뀌겠지만 현재까지 이해한 것을 바탕으로 써보겠다

std::thread

대입 연산자를 이용하여 함수를 실행하기 전까지는 빈 포인터 상태처럼 남아있다

void Work()
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
}

int main()
{
	std::thread t = std::thread(Work);
	t.join();
}

스레드는 생성과 동시에 실행된다

그리고 이런 스레드에 대해 관리하는 방법은 2가지가 있다

join();

현재 스레드를 지정한 스레드가 끝나기전까지 재운다

그리고 유의할 점은 스레드가 끝난다고해서 std::thread가 죽진 않는다

detach();

“분리한다”라는 이 함수는 현재 스레드에서 실행 중인 스레드를 분리함으로서 독립적으로 수행 할 수 있게한다

void Independent()
{
	std::cout << "Start Independent Work" << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(5));
	std::cout << "End Independent Work" << std::endl;
}

void Caller()
{
	std::cout << "Start Call Work" << std::endl;
	std::thread t = std::thread(Independent);
	std::this_thread::sleep_for(std::chrono::seconds(2));
	std::cout << "End Call Work" << std::endl;
}

int main()
{
	std::thread t = std::thread(Caller);
	std::this_thread::sleep_for(std::chrono::seconds(10));
}

이 코드를 실행하게 되면 문제가 생긴다

바로 “End Independent Work”가 출력되지 않는다는 것인데, 그 이유는 std::thread는 기본적으로 스코프에 의해 생명이 관리되기 때문이다

그렇기에 5초가 지나 출력되기전에 2초가 지나버려 스레드 자체가 사라진다는 의미!

따라서 우리가 원하는 방향으로 실행하려면

void Independent()
{
	std::cout << "Start Independent Work" << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(5));
	std::cout << "End Independent Work" << std::endl;
}

void Caller()
{
	std::cout << "Start Call Work" << std::endl;
	std::thread t = std::thread(Independent);
	t.detach();
	std::this_thread::sleep_for(std::chrono::seconds(2));
	std::cout << "End Call Work" << std::endl;
}

int main()
{
	std::thread t = std::thread(Caller);
	std::this_thread::sleep_for(std::chrono::seconds(10));
}

다음과 같이 작성하면 된다

std::mutex

mutex는 일종의 간이영역이라고 생각하면 된다

mutex를 lock 상태로 만들어 범위 내, 혹은 스코프 내의 변수들을 보호한다

뭐로부터 보호하냐? 바로 다른 스레드들로부터이다

애초에 ‘멀티’스레드이기 때문에 다른 스레드들이 평행세계마냥 동시에 같이 돌아간다

그렇기에 같은 변수에 접근하려고 할 때 중첩이 된다거나 누락이 된다거나하는 문제들이 발생하기도 한다

그렇기에 이 변수를 수정할때 다른 스레드들은 건들지 못하도록 잠시 보호막, 이 스레드만 수정할 수 있는 권한을 부여하는 것이다

그럼 lock은 어떻게 하는지 알아보도록하자

std::lock_guard

std::mutex m;
{
    std::lock_guard<std::mutex> lock(m);
    sharedData++;
}

lock_guard는 가장 간단한 보호기로 mutex를 스코프 기준으로 보호한다

범위: 생성~스코프 끝날때까지

std::unique_lock

std::mutex m;
std::unique_lock<std::mutex> lock(m);
sharedData++;
lock.unlock(); 
// 다시 lock 가능
lock.lock();    

좀 더 고급기능이 있는 보호기다

우선 스코프와 상관없이 해제 후 다시 lock이 가능하다

그리고 가장 중요한 이유로는 후술 할 std::condition_variable과 연동이 가능하다

std::condition_variable

이 친구는 잠든 스레드를 깨워서 자신의 mutex 권한이 있는지 확인해보라고 하나의 스레드 혹은 모든 스레드에게 전달할 수 있다

cv.wait(lock, [] {
    return !jobQueue.empty();
});

이 상태의 코드가 실행될 때 jobQueue에 요소가 있다면 그대로 실행되지만 요소가 없다면 잠들게 된다

cv.wait(lock, bool condition);

위에서는 람다식으로 표현했지만 그냥 bool 조건 검사이다

그 후 다시 한번 조건에 부합되는지 확인하기 위해서는 “조건이 변경되어서 참일 수도 있으니 한번 확인해봐라” 라고 알림을 보낼 수 있다

cv.notify_one(); //무작위 하나만 검사 요청
cv.notify_all(); //cv로 wait하는 모든 스레드에 검사 요청

만약 검사를 진행했더니 조건이 참이라면, 해당 wait 아래에 있는 코드들을 mutex 권한을 가진채로 실행한다

 


현재 스레드풀을 만들며 감을 잡아가고 있지만 앞으로 어떤 곳에 어떻게 쓰일지, 얼마나 더 어려워질지는 두고봐야할 것이다

우선은 사용하는 방법을 알았다는 것에 감사하자

'C++과 친해지기' 카테고리의 다른 글

Fight Club - Rule of 0/3/5  (0) 2025.12.13