ue4-多线程使用
主线程异步或多线程异步的使用
在GameThread线程之外的其他线程中,不允许做一下事情
不要 spawning / modifying / deleting UObjects / AActors
不要使用定时器 TimerManager
不要使用任何绘制接口,例如 DrawDebugLine
如果想在主线程中异步处理(也就是分帧处理),可以使用一下接口(在 Async.h 中)
1
2
3
4AsyncTask(ENamedThreads::GameThread, [&]() {
UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncTask"));
SpawnActor(3);
});
多线程使用
新开线程线程的类
PrimeNumberWorker.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39class UMyGameInstance;
//~~~~~ Multi Threading ~~~
class FPrimeNumberWorker : public FRunnable
{
static FPrimeNumberWorker* Runnable;
FRunnableThread* Thread;
TArray<uint32>* PrimeNumbers;
UMyGameInstance* mGameIns;
FThreadSafeCounter StopTaskCounter; //用于多线程间的判断交互,volatile int32 Counter;
int32 FindNextPrimeNumber();
FCriticalSection QueueCritical; //互斥锁
FEvent* ThreadSuspendedEvent; //线程悬挂和唤醒事件
public:
bool IsFinished();
void Suspend();
void Resume();
//Constructor / Destructor
FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns);
virtual ~FPrimeNumberWorker();
// Begin FRunnable interface.
virtual bool Init();
virtual uint32 Run();
virtual void Stop();
// End FRunnable interface
/** Makes sure this thread has stopped properly */
void EnsureCompletion();
FCriticalSection* GetCriticalSection();
static FPrimeNumberWorker* JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns);
static FPrimeNumberWorker* Get();
static void Shutdown();
static bool IsThreadFinished();
};PrimeNumberWorker.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
FPrimeNumberWorker* FPrimeNumberWorker::Runnable = nullptr;
FPrimeNumberWorker::FPrimeNumberWorker(TArray<uint32>& TheArray, UMyGameInstance* GameIns)
: mGameIns(GameIns)
, StopTaskCounter(0)
{
ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool();
PrimeNumbers = &TheArray;
Thread = FRunnableThread::Create(this, TEXT("FPrimeNumberWorker"), 0, TPri_BelowNormal); //windows default = 8mb for thread, could specify more
}
FPrimeNumberWorker::~FPrimeNumberWorker()
{
delete Thread;
Thread = nullptr;
FPlatformProcess::ReturnSynchEventToPool(ThreadSuspendedEvent);
ThreadSuspendedEvent = nullptr;
}
bool FPrimeNumberWorker::Init()
{
PrimeNumbers->Empty();
return true;
}
uint32 FPrimeNumberWorker::Run()
{
//Initial wait before starting
FPlatformProcess::Sleep(0.03);
while (StopTaskCounter.GetValue() == 0 && !IsFinished())
{
FScopeLock* QueueLock = new FScopeLock(&QueueCritical); //锁住
//***************************************
//不要 spawning / modifying / deleting UObjects / AActors 等等之类的事
//这里做多线程间共享信息的 modify,如:PrimeNumbers->Add
//***************************************
PrimeNumbers->Add(FindNextPrimeNumber());
UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, lock"));
//Suspend();
//prevent thread from using too many resources
FPlatformProcess::Sleep(1.0f); //这里睡眠3秒是为了让GameThread中的UMyGameInstance::MyAsyncThread中的日志打不出来
delete QueueLock;//解锁
UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::Run, unlock"));
//FPlatformProcess::Sleep(2.0f); //这里睡眠2秒是为了让GameThread中获取到 互斥锁QueueCritical 并锁住
}
Stop();
return 0;
}
bool FPrimeNumberWorker::IsFinished()
{
return PrimeNumbers->Num() == 5;
}
void FPrimeNumberWorker::Suspend()
{
ThreadSuspendedEvent->Wait();
}
void FPrimeNumberWorker::Resume()
{
ThreadSuspendedEvent->Trigger();
}
void FPrimeNumberWorker::Stop()
{
StopTaskCounter.Increment();
}
FPrimeNumberWorker* FPrimeNumberWorker::JoyInit(TArray<uint32>& TheArray, UMyGameInstance* GameIns)
{
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new FPrimeNumberWorker(TheArray, GameIns);
}
bool isSupport = FPlatformProcess::SupportsMultithreading();
FString msg = isSupport ? "SupportsMultithread" : "dont SupportsMultithreading";
UE_LOG(LogMyTest, Warning, TEXT("--- FPrimeNumberWorker::JoyInit, msg:%s"), *msg);
return Runnable;
}
FPrimeNumberWorker* FPrimeNumberWorker::Get()
{
return Runnable;
}
void FPrimeNumberWorker::EnsureCompletion()
{
Stop();
Thread->WaitForCompletion();
}
FCriticalSection* FPrimeNumberWorker::GetCriticalSection()
{
return &QueueCritical;
}
void FPrimeNumberWorker::Shutdown()
{
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = nullptr;
}
}
bool FPrimeNumberWorker::IsThreadFinished()
{
if (Runnable) return Runnable->IsFinished();
return true;
}
int32 FPrimeNumberWorker::FindNextPrimeNumber()
{
int32 TestPrime = 123;
return TestPrime;
}再来个测试方法,写在GameInstanece的扩展类中(习惯了测试用 console命令调方法,不懂的看这里 ue4-控制台执行方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32UFUNCTION(Exec)
virtual void MyAsyncSuspend();
UFUNCTION(Exec)
virtual void MyAsyncResume();
UFUNCTION(Exec)
virtual void MyAsyncThread();
void UMyGameInstance::MyAsyncSuspend()
{
FPrimeNumberWorker::Get()->Suspend();
}
void UMyGameInstance::MyAsyncResume()
{
FPrimeNumberWorker::Get()->Resume();
}
void UMyGameInstance::MyAsyncThread()
{
mNumVec.Empty();
FPrimeNumberWorker::JoyInit(mNumVec, this);
GetTimerManager().SetTimer(mTimer1, [&]()->void {
FPrimeNumberWorker* pnw = FPrimeNumberWorker::Get();
if (!pnw) return;
FCriticalSection* cs = pnw->GetCriticalSection(); //获取FPrimeNumberWorker到中的互斥锁QueueCritical
FScopeLock QueueLock(cs);//锁住,等作用域过后QueueLock自动析构解锁
UE_LOG(LogMyTest, Warning, TEXT("--- UMyGameInstance::MyAsyncThread, mNumVec.Num=%d"), mNumVec.Num());
if (pnw->IsThreadFinished())
FPrimeNumberWorker::Shutdown();
}, 1.0f, true);
}- 测试互斥。 上面的 FPrimeNumberWorker 里面的代码方法是有加锁的,在控制台输入指令
MyAsyncThread
可测试。注释掉FScopeLock QueueLock(cs);
这一行代码就能看出没有互斥锁的区别。 - 测试线程的悬挂和唤醒。
- 取消注释 FPrimeNumberWorker 里面的
//Suspend();
- 注释掉 MyAsyncThread 测试方法中的的定时器
GetTimerManager
(因为其他线程中在 未解锁 的 情况下 悬挂 的话,主线程获取不到这个锁会一直等待,也就是看起来像卡死了) - 在控制台输入指令
MyAsyncThread
,就可以 Run 方法的线程悬挂住该线程 - 在控制台输入指令
MyAsyncResume
,就可以 唤醒之前调用ThreadSuspendedEvent->Wait();
的线程。
- 取消注释 FPrimeNumberWorker 里面的
- 测试互斥。 上面的 FPrimeNumberWorker 里面的代码方法是有加锁的,在控制台输入指令
线程唤醒、悬挂
- FEvent* ThreadSuspendedEvent;
- 线程悬挂。A线程中调用
ThreadSuspendedEvent->Wait();
,A线程就会悬挂主,必须由B线程调用ThreadSuspendedEvent->Trigger();
才能唤醒A线程。 - 所以如果直接在主线程(GameThread)中调用
ThreadSuspendedEvent->Wait();
,那就看起像卡死了。
编辑器模式下进行多线程开发注意事项
PIE模式下,与新开的线程交互正常;如果退出PIE模式,PIE中的实例对象都会被标记为坐等回收的对象 InValid,此时如果新开的线程还在Run中跑用到PIE中的实例对象,将造成 编辑器崩溃。
最好在你的游戏退出时 FPrimeNumberWorker ShutDown一下,方便下次测试。
1
2
3
4
5void UMyGameInstance::Shutdown()
{
FPrimeNumberWorker::Shutdown();
Super::Shutdown();
}