[UE5] 언리얼 엔진 멀티스레드 프로그래밍의 필수 도구: FThreadSafeBool와 FThreadSafeCounter 제대로 알아보기

ChangJin·2024년 10월 19일
0

Unreal Engine5

목록 보기
105/114
post-thumbnail

이 글에서는 멀티스레드 프로그래밍에서 필수적인 도구인 FThreadSafeBool와 FThreadSafeCounter에 대해 다룹니다. 이 두 도구는 언리얼 엔진에서 여러 스레드가 동시에 데이터를 처리할 때 발생하는 레이스 컨디션을 방지하고, 스레드 간의 안전한 데이터 처리를 보장하는 데 사용됩니다.

FThreadSafeBool: 멀티스레드 환경에서 안전하게 bool 값을 관리.
FThreadSafeCounter: 여러 스레드에서 안전하게 정수 값을 증가/감소.
또한, 왜 일반적인 bool이나 int를 사용하면 안 되는지, 그리고 실제 게임 인스턴스 매니저 시스템과의 연계 방법까지 설명합니다.

멀티스레드 환경에서 안전한 프로그래밍: 왜 FThreadSafeBool와 FThreadSafeCounter를 써야 할까?

멀티스레드 프로그래밍은 성능 최적화에 매우 중요하지만, 그만큼 신경 써야 할 점도 많습니다. 특히 여러 스레드가 하나의 변수를 동시에 접근할 때 발생하는 '레이스 컨디션' 문제는 프로그램이 예측할 수 없는 결과를 낳을 수 있습니다. 그래서 언리얼 엔진에서는 이런 문제를 해결하기 위한 두 가지 중요한 도구를 제공합니다: FThreadSafeBoolFThreadSafeCounter입니다.


1. FThreadSafeBool: 스레드 안전성을 보장하는 Boolean 변수

왜 필요할까?

기본적인 bool 값은 여러 스레드가 동시에 접근할 때, 값이 불안정하게 변경될 수 있습니다. 예를 들어, 한 스레드에서 true로 변경했는데, 다른 스레드가 그 값을 잘못 읽어 false로 인식하는 상황이 발생할 수 있습니다. 이런 상황을 방지하기 위해 FThreadSafeBool이 필요합니다. 이 클래스는 멀티스레드 환경에서 안전하게 boolean 값을 관리할 수 있도록 설계되었습니다.

사용 예시: 렌더링 스레드와 게임 스레드 간 안전한 데이터 공유

멀티스레드 프로그래밍에서는 렌더링 스레드와 게임 로직 스레드가 동시에 같은 객체에 접근할 수 있습니다. 이런 경우 객체의 상태가 중간에 변경되면 잘못된 렌더링 결과가 나올 수 있습니다. 이를 방지하려면 FThreadSafeBool을 사용하여 상태를 안전하게 공유해야 합니다.

FThreadSafeBool bIsVisible;

void UpdateVisibility(bool bNewVisibility)
{
    bIsVisible.Set(bNewVisibility); // 멀티스레드 안전하게 상태 업데이트
}

bool GetVisibility() const
{
    return bIsVisible.Get();
}

이처럼 FThreadSafeBool은 멀티스레드 환경에서 bool 값을 안정적으로 공유할 수 있는 도구입니다.


2. FThreadSafeCounter: 멀티스레드 카운터를 안전하게 관리하라

FThreadSafeCounter가 왜 중요한가?

게임에서 자주 쓰이는 기능 중 하나는 여러 스레드에서 데이터를 카운팅하는 것입니다. 예를 들어, 게임 내에서 플레이어의 총알 개수를 스레드 안전하게 관리해야 한다면, FThreadSafeCounter가 매우 유용합니다. 기본적인 정수형 변수를 사용하면 여러 스레드가 동시에 값을 변경할 때 레이스 컨디션이 발생할 수 있어 예상치 못한 결과를 초래할 수 있습니다. FThreadSafeCounter는 이런 문제를 방지하기 위해 설계되었습니다.

사용 예시: 작업 진행도 추적

다양한 스레드에서 게임 작업을 동시에 처리하면서, 작업이 얼마나 진행되었는지를 추적하려면 FThreadSafeCounter를 사용할 수 있습니다. 잠금을 사용하지 않고도 안전하게 값의 증감이 가능합니다.

FThreadSafeCounter TaskCounter;

void IncrementTask()
{
    TaskCounter.Increment(); // 안전하게 증가
}

int32 GetTaskCount() const
{
    return TaskCounter.GetValue(); // 현재 카운트 반환
}

이렇게 하면 작업이 얼마나 진행되었는지를 안전하게 추적할 수 있습니다.


3. 왜 일반적인 bool이나 int를 사용하면 안 되는가?

일반적인 bool이나 int는 멀티스레드 환경에서 안전하지 않습니다. 그 이유는 간단합니다. 여러 스레드가 하나의 변수를 동시에 읽고 쓸 때, 값이 예측하지 못한 상태로 변경될 수 있기 때문입니다. 이를 레이스 컨디션(Race Condition)이라고 부릅니다.

레이스 컨디션이란?

레이스 컨디션은 두 개 이상의 스레드가 같은 데이터를 동시에 읽거나 쓸 때 발생하는 문제입니다. 이런 상황에서는 스레드가 데이터를 수정하는 시점에 따라 값이 달라질 수 있기 때문에, 데이터의 무결성이 손상될 수 있습니다.


4. FThreadSafeBool와 FThreadSafeCounter로 안전한 멀티스레드 프로그래밍

FThreadSafeBool와 FThreadSafeCounter는 어떻게 해결할까?

FThreadSafeBool는 멀티스레드에서 안전하게 bool 값을 읽고 쓸 수 있게 보장해줍니다. 마찬가지로 FThreadSafeCounter는 동시성 문제를 방지하면서 정수 값을 안전하게 증감시킬 수 있는 방법을 제공합니다. 이 두 클래스는 값의 변경을 원자적으로(Atomic) 처리하기 때문에, 각 스레드가 데이터에 접근할 때마다 항상 안전하게 값을 읽고 쓸 수 있습니다.

사용 시 주의할 점

FThreadSafeBool와 FThreadSafeCounter를 사용할 때도 신중하게 사용해야 합니다. 주의해야 할 점은 두 개 이상의 데이터를 동시에 수정하는 상황입니다. 이럴 때는 을 사용하거나, 트랜잭션 방식으로 처리해야 합니다. 예를 들어, 한 스레드에서 FThreadSafeBool와 FThreadSafeCounter를 동시에 변경하고 다른 스레드에서 이를 읽는다면, 데이터의 불일치가 발생할 수 있습니다.


5. 게임 인스턴스 매니저 시스템과의 연계

멀티스레드 환경에서는 게임 상태나 리소스를 관리하는 매니저 시스템도 스레드 안전성을 보장해야 합니다. 이때 FThreadSafeBool와 FThreadSafeCounter는 매우 유용하게 사용될 수 있습니다. 게임 인스턴스에서 전역적으로 접근할 수 있는 매니저들을 UGameInstanceSubsystem을 통해 관리하면서, 동시에 스레드 안전성을 보장하는 방법을 고민해볼 필요가 있습니다.

예시: 게임 인스턴스에서 레벨 매니저를 초기화할 때 스레드 안전성을 보장하는 코드

class UCJGameInstanceSubsystem : public UGameInstanceSubsystem
{
public:
    FThreadSafeCounter ManagerInitializationCount;
    FThreadSafeBool bIsManagerInitialized;

    virtual void InitializeManagersForLevel(const FString& LevelName)
    {
        if (!bIsManagerInitialized)
        {
            // 매니저 초기화 로직
            ManagerInitializationCount.Increment();  // 스레드 간 안전하게 카운터 증가

            bIsManagerInitialized = true;
            UE_LOG(LogTemp, Warning, TEXT("Manager initialized for level: %s"), *LevelName);
        }
    }

    virtual void ShutdownManagers()
    {
        if (bIsManagerInitialized)
        {
            ManagerInitializationCount.Decrement();  // 스레드 간 안전하게 카운터 감소
            bIsManagerInitialized = false;
        }
    }
};

예를 들어, 여러 스레드에서 작업이 끝났는지를 추적하는 로직에 FThreadSafeCounter를 사용하면, 여러 작업이 동시에 진행되더라도 문제없이 카운팅이 가능합니다.


6. FThreadSafeBool와 FThreadSafeCounter로 문제 해결하기

멀티스레드 환경에서 발생하는 데이터 무결성 문제는 게임 성능이나 안정성에 치명적인 영향을 미칠 수 있습니다. 언리얼 엔진은 이런 문제를 해결하기 위해 다양한 스레드 안전 도구를 제공합니다. 그중에서도 FThreadSafeBool와 FThreadSafeCounter는 필수적인 도구입니다.

FThreadSafeBool는 bool 값을 여러 스레드에서 안전하게 공유할 수 있게 해주며, FThreadSafeCounter는 작업의 진행 상황을 안전하게 추적할 수 있습니다. 이를 적절히 사용하면, 게임의 성능을 최적화하고 안정성을 높일 수 있습니다.

결론적으로, 언리얼 엔진에서 멀티스레드 프로그래밍을 할 때, FThreadSafeBool와 FThreadSafeCounter는 필수적인 도구로 활용될 수 있으며, 이를 통해 레이스 컨디션 문제를 예방하고, 안전하게 데이터를 처리할 수 있습니다.


언리얼 엔진에서 제공하는 기타 스레드 안전 도구

언리얼 엔진은 멀티스레드 프로그래밍에서 다양한 스레드 안전 도구를 제공합니다. 여기 몇 가지 중요한 도구들을 소개합니다:

  1. FCriticalSection: 잠금(lock) 메커니즘을 사용하여 스레드 간의 상호 배제를 보장하는 도구.
  2. FEvent: 스레드 간의 비동기 신호 전달을 관리하는 도구로, 특정 조건이 충족될 때 스레드를 깨웁니다.
  3. FQueuedThreadPool: 여러 스레드에서 작업을 큐에 넣고 스레드 풀에서 실행시키는 방식으로 스레드 관리.
  4. FPlatformProcess::Sleep(): 스레드의 실행을 일정 시간 멈추게 하여, 리소스 사용을 조절하거나 타이밍 제어를 할 때 사용.
  5. FPlatformAtomics: 원자적 연산을 지원하는 함수들을 모은 클래스.
  6. TLockFreePointerList: 잠금 없이 스레드 간의 안전한 포인터 리스트 관리를 지원.
  7. FThreadSafeQueue: 스레드 안전한 큐(queue) 구현으로, 여러 스레드에서 안전하게 데이터를 넣고 꺼낼 수 있음.

이러한 도구들은 스레드 간의 동기화와 데이터 무결성을 보장하며, 멀티스레드 환경에서 발생할 수 있는 다양한 문제를 해결합니다.


결론

멀티스레드 환경에서는 스레드 간의 동기화가 필수적입니다. 일반적인 bool이나 int 같은 데이터 타입은 레이스 컨디션으로 인해 값이 예측하지 못한 방식으로 변경될 수 있습니다. 이러한 문제를 해결하기 위해 언리얼 엔진은 FThreadSafeBoolFThreadSafeCounter 같은 도구를 제공합니다. 이를 활용해 게임 인스턴스 매니저 시스템의 상태를 안전하게 관리하고, 멀티스레드 환경에서 발생할 수 있는 문제를 예방할 수 있습니다.

profile
게임 프로그래머

0개의 댓글