从分离线程停止主游戏循环的最佳方法

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(),并使这变得毫无意义。我认为我很有可能误解了这一点!

相反,我在一个类中设置了两个bools,作为对线程的引用传递。当线程将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_mutexstd::mutex,它们似乎正是您想要的。

至于从多个线程访问渲染器 - 嗯,不太好。 SDL documentation 明确指出您真的不应该(除非您绝对确定正在使用 SDL 的渲染器实现并且知道该实现的行为方式)。考虑改变你的方法 - 例如在单独的线程中渲染到内存缓冲区/表面,然后发出信号,以便渲染线程可以获取缓冲区并将其转换为纹理。您甚至可以在此处进行双缓冲以最大程度地减少停顿。

有些事情可以改进但需要更多工作:创建线程不是免费的,为什么不创建一次并等待 mutex/semaphore/condvar 信号以呈现下一帧? SDL_CreateTextureFromSurface 假定为静态纹理,为什么不创建一次流式纹理并使用 SDL_LockTexture && memcpy 更新它?

【讨论】:

以上是关于从分离线程停止主游戏循环的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

Android - 停止线程的最佳和安全方法

unity发布的android游戏有办法让它在后台运行时主线程不停止吗

求大神指导VB.net 线程 ThreadState 的使用方法 怎么获取线程状态,是不是被挂起堵塞停止等等

Libev C++ 无法停止多线程应用程序中的动态循环

在 C++ 中的线程内启动和停止循环

Thread的中断机制(interrupt),循环线程停止的方法