从分离线程停止主游戏循环的最佳方法
Posted
技术标签:
【中文标题】从分离线程停止主游戏循环的最佳方法【英文标题】:Best method for stopping the main game loop from a detached thread 【发布时间】:2020-04-05 17:08:04 【问题描述】:我正在使用 SDL 开发 C++ 游戏。我开始关注Lazyfoo's tutorials,并一直在使用 XCode 在 MacOS 中构建它。这个周末,我一直在查看是否可以在 Visual Studio 2019 中的 Windows 上编译它。我很成功,但我不确定我必须做出的改变之一。
游戏使用 ImageMagick (Magick++) 进行一些绘图,然后使用 SDL 将其作为纹理存储在视频卡内存中(SDL 自己的绘图功能还不够)。绘图部分非常慢,但不需要同步发生,所以我选择将该进程放在它自己的线程中,这在我的 Mac 上运行得非常好。
现在我在 Windows 上运行它,当游戏尝试绘制新角色时,我偶尔会看到崩溃。我将其缩小到SDL_CreateTextureFromSurface()
,它是从线程内调用的,并使用从 Magick++ 图像创建的表面创建新纹理。这只发生在我使用多线程时,如果我禁用它(并在绘制某些东西时终止我的帧率),它工作正常。
我查看了互斥锁,但所有建议都建议在线程上使用join
,这会在整个绘图期间阻塞main()
,并使这变得毫无意义。我认为我很有可能误解了这一点!
相反,我在一个类中设置了两个bool
s,作为对线程的引用传递。当线程将SDL_Thread_Waiting
设置为true
时,main()
中的游戏循环会暂时暂停并将SDL_Main_Paused
设置为true
,这告诉线程它可以安全地访问渲染器。然后线程在完成后将SDL_Main_Paused
设置为false
,游戏循环继续。
这意味着 ImageMagick 可以在它自己的线程中尽可能慢,但在最后一步,我可以短暂暂停主游戏以允许存储纹理而不会发生任何崩溃。
下面的示例代码。游戏在这一点上非常庞大,但这应该表明我在做什么:
// class.hpp
Class Game
Public:
bool is_running;
bool SDL_Thread_Waiting = false;
bool SDL_Main_Paused = false;
// ...
// main.cpp
Game game;
int main(int argc, char* argv[])
game.is_running = true;
while (game.is_running)
// Regular game stuff!
if (game.SDL_Thread_Waiting)
game.SDL_Main_Paused = true;
while (game.SDL_Main_Paused)
// do.. nothing?
// Threaded bits
void generate_character()
std::thread th(make_texture, std::ref(game));
th.detach();
SDL_Texture* make_texture(Game& game)
// Heavy lifting by ImageMagick goes here
SDL_Thread_Waiting = true;
while (!SDL_Main_Paused)
std::this_thread::sleep_for(std::chrono::milliseconds(25));
// SDL_CreateTextureFromSurface()
SDL_Main_Paused = false;
SDL_Thread_Waiting = false;
return texture;
这是从一个分离的线程中短暂阻塞主游戏循环的合适方法吗?我是否误解了mutex
,这更适合这里吗?
我对 C++ 和 SDL 非常陌生,如果我的整个问题是基于进一步的误解,我深表歉意!
【问题讨论】:
在一个线程中写入并在另一个线程中读取的bool
变量需要为std::atomic<booL>
。也看看 [std::condition_variable
](en.cppreference.com/w/cpp/thread/condition_variable
【参考方案1】:
您所描述的是互斥体行为。您已经实现了糟糕的自旋锁 - 这可能会因为锁变量上的错误同步而失败(一个线程可能会设置变量但其他线程尚未看到此更改),并且在锁定时会浪费 CPU 周期,因为它会一直在循环中旋转。使用SDL_mutex
或std::mutex
,它们似乎正是您想要的。
至于从多个线程访问渲染器 - 嗯,不太好。 SDL documentation 明确指出您真的不应该(除非您绝对确定正在使用 SDL 的渲染器实现并且知道该实现的行为方式)。考虑改变你的方法 - 例如在单独的线程中渲染到内存缓冲区/表面,然后发出信号,以便渲染线程可以获取缓冲区并将其转换为纹理。您甚至可以在此处进行双缓冲以最大程度地减少停顿。
有些事情可以改进但需要更多工作:创建线程不是免费的,为什么不创建一次并等待 mutex/semaphore/condvar 信号以呈现下一帧? SDL_CreateTextureFromSurface
假定为静态纹理,为什么不创建一次流式纹理并使用 SDL_LockTexture
&& memcpy
更新它?
【讨论】:
以上是关于从分离线程停止主游戏循环的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章
unity发布的android游戏有办法让它在后台运行时主线程不停止吗