【发布时间】:2020-12-26 00:12:12
【问题描述】:
我有一个相当奇怪的例子,所以我将在这里简要介绍一下上下文,希望我们可以假装这是一个好主意。
我正在使用需要定期调用其 FRAME() 宏的分析器,以便它知道游戏的 CPU 帧在哪里开始和结束(宏构建的对象是基于 RAII/范围的)。我正在为我的线程使用光纤(主“线程”也是一个光纤工作线程),并且这个分析宏只支持从一个未在分析器中注册为光纤工作线程的线程调用。因此,我在短期内有这个糟糕的解决方案,我只为这个宏与一个单独的线程通信。目标是在此单独线程上尽可能准确地获取 RAII 对象的构造/销毁时间,而不会中断调用线程的时间。但有时,整个应用程序都会挂起。我不明白这怎么可能。
主“线程”(实际上是在光纤上,但没关系)/游戏循环:
FrameProfile frameProfile("Client Update");
while (!bShouldQuit)
{
frameProfile.StartFrame();
/* Do the game client's work for this frame */
frameProfile.EndFrame();
}
然后这个 FrameProfile 对象负责启动一个单独的线程,并在从上面调用 StartFrame 时让该线程进入 FRAME 宏范围,并且该线程将在该范围内休眠,直到调用 EndFrame,此时指向它将唤醒并退出范围,破坏分析器的帧测量对象,并为我们提供一个希望准确的帧时间。
struct FrameProfile
{
FrameProfile(const char* tag)
{
pthread_ = std::make_unique<std::thread>(
[tag, this](std::atomic_bool& killFlag) {
while (!killFlag)
{
assert(!endThreadFrame.WasSignalled());
startThreadFrame.WaitConsume();
{
assert(!startThreadFrame.WasSignalled());
assert(!endedThreadFrame.WasSignalled());
// Construct the frame-measuring object using this macro
OPTICK_FRAME(tag);
startedThreadFrame.Signal();
endThreadFrame.WaitConsume();
// endThreadFrame has been signalled - we need to exit scope
// to finish measuring ASAP
}
assert(!endThreadFrame.WasSignalled());
endedThreadFrame.Signal();
}
},
std::ref(bKill_)
);
}
~FrameProfile()
{
bKill_ = true;
if (pthread_)
{
if (pthread_->joinable())
{
pthread_->join();
}
}
}
void StartFrame()
{
assert(!startThreadFrame.WasSignalled());
assert(!startedThreadFrame.WasSignalled());
// Tell thread to start measuring the frame
startThreadFrame.Signal();
// Wait for thread to have started frame measurement
startedThreadFrame.WaitConsume();
}
void EndFrame()
{
assert(!endThreadFrame.WasSignalled());
assert(!endedThreadFrame.WasSignalled());
// Tell thread to end frame measurement
endThreadFrame.Signal();
// Wait for thread to have ended frame measurement
endedThreadFrame.WaitConsume();
}
private:
std::unique_ptr<std::thread> pthread_;
std::atomic_bool bKill_ = false;
struct ThreadSignal
{
std::atomic_bool bSignalled;
std::mutex mutex;
std::condition_variable cv;
void Signal()
{
assert(!bSignalled);
{
std::unique_lock<std::mutex> _(mutex);
bSignalled = true;
}
cv.notify_all();
}
bool WasSignalled()
{
return bSignalled;
}
void WaitConsume()
{
std::unique_lock unique(mutex);
cv.wait(unique, [this]() { return bSignalled == true; });
unique.unlock();
bSignalled = false;
}
};
ThreadSignal startThreadFrame;
ThreadSignal endThreadFrame;
ThreadSignal startedThreadFrame;
ThreadSignal endedThreadFrame;
};
你能发现我在这里做错了什么吗?甚至是更好的解决方案,我愿意接受!这种情况很少见,但有时会挂起 - 'ThreadSignal' 对象之一将其布尔值设为 'true',但仍会卡住 - 我想这里有一个罕见的时间问题。
非常感谢!一直在扯我的头发。
【问题讨论】:
-
这并没有解决问题,但是当你启动一个 C++ 线程并且你不分离它时,它是可连接的。所以不需要
if (pthread_->joinable()) pthread_->join()中的测试。 -
“但有时,整个应用程序都会挂起。我不明白这怎么可能。” -- 当您的应用程序挂起时,您可能希望使用调试器来检查程序挂起时的状态。特别是各个线程的调用堆栈可能包含有用的信息。例如,它应该显示线程是否正在等待互斥体/条件变量。
-
@AndreasWenzel 确实,这就是我发现
one of the 'ThreadSignal' objects will have its bool as 'true', but will still be stuck所做的。我只是不知道如果这种双向信号不会让线程不同步,这在技术上是如何实现的——我认为不可能进行比赛:)
标签: c++ multithreading communication race-condition