나만의 작은 도서관
[TIL][C++] 250521 MMO 서버 개발 22일차: 언리얼에서 쓰레드 만들기, 다시 상기시키는 TSharedPtr<PacketSession>(this);의 문제점 등등… 본문
Today I Learn
[TIL][C++] 250521 MMO 서버 개발 22일차: 언리얼에서 쓰레드 만들기, 다시 상기시키는 TSharedPtr<PacketSession>(this);의 문제점 등등…
pledge24 2025. 5. 21. 23:16주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
언리얼에서 스레드 만들기
- 언리얼은 스레드를 만들 때 독자적인 방식으로 만들 수 있다. std::thread처럼 표준을 이용한 스레드 생성도 할 수는 있지만, 표준으로 만들 경우 여러 호환성 문제가 발생할 수 있기 때문에 되도록이면 언리얼에서 제공하는 스레드 생성 방식을 적용하는 것이 좋다.
FRunnableThread
- 언리얼에서 스레드는 FRunnableThread이라는 클래스 객체로 관리한다. 즉, 새로운 스레드를 만들고 싶다면, FRunnableThread객체를 하나 만들어서 작업을 물려주면 된다.
- FRunnableThread객체를 만들려면 정적 멤버 함수인 FRunnableThread::Create()를 호출하여 생성해야 한다. 그런데 이 함수는 FRunnable 클래스의 포인터를 요구하기 때문에 우선적으로 FRunnable 클래스의 객체가 필요하다.
FRunnable
- FRunnable는 아래와 같은 구조를 지녔다. 단순히 Run(), Stop()과 같은 가상 함수들이 몇 개 있는 간단한 구조이다.
class CORE_API FRunnable
{
public:
virtual bool Init()
{
return true;
}
virtual uint32 Run() = 0;
virtual void Stop() { }
virtual void Exit() { }
virtual class FSingleThreadRunnable* GetSingleThreadInterface( )
{
return nullptr;
}
virtual ~FRunnable() { }
};
- 이 FRunnable은 추상 클래스이므로(Run이라는 순수 가상 함수가 존재), 그대로 쓰지는 않고 상속받아 사용해야 한다.
class P1_API RecvWorker : public FRunnable
{
public:
virtual bool Init() override;
virtual uint32 Run override;
virtual void Exit() override;
}
FRunnableThread::Create()
- 이제 FRunnable을 상속받은 RecvWorker는 Create를 호출할 준비가 끝났다. RecvWorker 멤버 함수에서 Create호출을 하고 반환값으로 스레드 포인터를 들고 있으면 된다. (결국엔 create에 this 포인터 넘겨주려고 FRunnable을 상속받은 것이다.)
// RecvWorker 멤버 변수
FRunnableThread* Thread
// RecvWorker 멤버 함수 내부(ex. 생성자 내부 코드)
Thread = FRunnableThread::Create(this, TEXT("RecvWorkerThread"));
Create호출 후 워크 플로우
- FRunnbleThread::Create()를 호출하면 표준과 달리 자동으로 멤버 함수들을 차례대로 실행한다. 흐름(워크 플로우)은 아래와 같다.
- Create() 호출됨
- FRunnable::Init() 호출됨
- true가 반환되면, FRunnable::Run()이 호출됨
- Run()이 실행된다. 이제부터 스레드가 분리되어 실행된다.
- Run이 종료되면 Exit()이 호출된다.
주의할 점
- 언리얼은 특이하게 에디터를 종료해도 분리한 스레드가 알아서 종료되지 않는다. 따라서, 분리한 스레드는 반드시 어떻게든 신호를 줘서 종료를 명시적으로 해줘야 한다.
다시 상기시키는 TSharedPtr <PacketSession>(this);의 문제점
- 생포인터를 생성자 인자로 넣게 되는 순간 본인이 refCount를 세는 첫 번째 대상인 것처럼 행동한다.
- 따라서, 만약 다른 곳에서 이미 refCount를 세고 있었다면 양쪽에서 refCount를 관리하는 상황이 생긴다. 이는 잘못된 refCount관리이므로, refCount를 하나로 관리하기 위해 shared_from_this()와 같은 함수를 사용해야 한다.(언리얼에서 shared_from_this() 역할을 하는 함수는 asShared()이다.)
언리얼 버전의 스마트 포인터 관련 함수
- TSharedFromThis <T>: enable_shared_from_this <T>의 언리얼 버전
- TSharedPtr <T>: shared_ptr <T>의 언리얼 버전
- weakPtr.Pin(): weakPtr.lock()의 언리얼 버전
- AsShared(): shared_from_this()의 언리얼 버전