[스레드 생성]

 

스레드는 시스템 콜을 통해 생성해줘야 하는데, 생성 방식은 운영체제마다 다릅니다. 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

+ Recent posts