[스레드 생성]
스레드는 시스템 콜을 통해 생성해줘야 하는데, 생성 방식은 운영체제마다 다릅니다. Windows에서는 <windows.h>를 include한 뒤, CreateThread() 함수를 호출하여 스레드를 생성할 수 있습니다.
#include <windows.h>
int main()
{
CreateThread();
}
하지만 위의 방식대로 스레드를 생성할 경우, CreateThread() 함수는 Windows 운영체제에서만 사용 가능한 API이기 때문에 다른 운영체제(플랫폼)에서는 제대로 코드가 동작하지 않는 호환성 문제가 발생할 수 있습니다.
이 문제를 해결하기 위하여 C++에서는 <thread.h>를 지원해줍니다.
#include <thread>
int main()
{
std::thread t;
}
[엔트리 포인트]
하지만 위와 같이 std::thread t;만 실시한다고 해서 스레드가 동작하는 것은 아닙니다. 생성 후 따로 관리해주지 않으면 스레드는 바로 소멸하는데, 이를 위해 스레드에 작업을 할당해줄 수 있습니다. 이것을 엔트리 포인트(Entry Point)라고 합니다. main 스레드의 경우에는 main() 함수가 엔트리 포인트가 됩니다.
#include <iostream>
#include <thread>
void HelloThread()
{
std::cout << "Hello Thread!" << std::endl;
}
int main()
{
std::thread t(HelloThread);
std::cout << "Hello world!" << std::endl;
}
[join]
엔트리 포인트 실행 후, 작업이 완료되면 스레드는 종료됩니다. 만약 스레드 t가 종료되기 전에 main이 종료되면 어떻게 될까요? 스레드는 병렬적으로 일을 처리하기 때문에 충분히 가능성있는 상황입니다.

바로 시스템 에러가 발생합니다.
이 문제를 해결하기 위한 방법으로, join() 함수가 있습니다. join() 함수를 사용하면 스레드가 종료되기 전까지 기다리는 동작을 수행합니다.
#include <iostream>
#include <thread>
void HelloThread()
{
std::cout << "Hello Thread!" << std::endl;
}
int main()
{
std::thread t(HelloThread);
std::cout << "Hello world!" << std::endl;
t.join();
}

위에서는 t에 엔트리 포인트를 할당한 뒤, join() 함수를 실행했습니다. 이러면 t의 작업(HelloThread() 함수)이 종료되기 전까지 main 함수는 종료되지 않게 됩니다.
[스레드 디버깅]
비주얼 스튜디오 환경에서, 스레드가 돌아가는 위치를 예측하여 중단점을 건 뒤, 디버깅을 실시하면 IDE 상단에서 현재 실행중인 스레드 목록을 확인할 수 있습니다.

[유용한 스레드 함수들]
C++에서 지원해주는 스레드에는 유용한 함수들이 있습니다.
| hardware_concurrency | - CPU core 갯수가 몇 개인지(논리적으로 실행할 수 있는 프로세스 수가 어떻게 되는지) 확인 - 100%확률로 정확하지 않고 경우에 따라선 0을 리턴할 수 있음 - int 자료형으로 반환값을 받으면 됨 |
| get_id | - 쓰레드는 생성되면(사용준비까지 완료) id를 부여받는데, 그 id가 뭔지 알 수 있음 - auto로 값을 반환 받아보면 알 수 있음 |
| detach | - 쓰레드는 생성되면 일단 생성한 주체 쓰레드와 연결되는데, 그 연결을 끊어주는 함수 - 이 함수는 백그라운드 쓰레드(데몬)를 만들어줌 - 하지만 이걸 써서 연결을 해제하면 hardware_concurrency, get_id 등 값을 확인하는 함수를 활용 할 수 없음 |
| join | - 쓰레드 동작 종료를 기다려주는 함수 |
| joinable | - detach 상태, 혹은 연동된 쓰레드가 없는 경우 해당 함수로 확인 가능 - 아무것도 없으면 0을 반환 - 예를 들어 쓰레드를 생성만 하고 엔트리 포인트에 무언가를 할당하지 않고 사용하는 경우가 있는 데, 이럴 경우 활용이 가능 <엔트리 포인트 동시 선언> std::thread temp(HelloThread); <엔트리 포인트 따로 선언> std::thread temp; temp = std::thread(HelloThread); |
[엔트리 포인트 지정 방식]
int main()
{
std::thread t(HelloThread);
}
엔트리 포인트는 스레드 생성과 동시에 바로 할당해줄 필요는 없고, 스레드를 생성해둔 후 나중에 따로 할당해줘도 됩니다. 이런 식으로 사용할 수 있는 이유는 아래와 같은 방식도 존재하기 때문입니다.
int main()
{
vector<thread> v;
// 미리 빈 객체 10개 사이즈 만들어서 하나하나 연동해서 넣는 방식
v.resize(10);
}
함수가 인자를 전달받는 경우에는 다음과 같이 활용하면 됩니다.
void HelloThread_2(int32 _Num)
{
cout << _Num << endl;
}
int main()
{
thread temp2(HelloThread_2, 10);
}
이것이 가능한 이유는 내부 로직을 확인해보면 알 수 있습니다.
public:
template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
_NODISCARD_CTOR_THREAD explicit thread(_Fn && _Fx, _Args&&... _Ax) {
_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
}
1번 인자는 함수 포인터 외에 callable 형태(함수 객체, 람다 등)를 받을 수 있다. 추가로 _Args&&... _AX는 베어리딕 템플릿입니다.
[1] template <class _Fnm class... _Args> ~ (_Fn&& _Fx, _Args&&... _Ax) << 인자 받으면
[2] _Start(STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...) << 여기에 전달하는 방식'서버' 카테고리의 다른 글
| [Server] 스핀락(Spinlock)과 sleep (0) | 2025.11.14 |
|---|---|
| [Server] 데드락(DeadLock) (0) | 2025.03.17 |
| [Server] Lock(mutex) (0) | 2025.03.17 |
| [Server] atomic (0) | 2025.03.17 |