You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ubuntu 23.10下SDL2与std::thread渲染冲突及退出方案咨询

问题解决:SDL2多线程渲染异常与退出响应兼容方案

问题根源

Ubuntu 23.10搭载的SDL2版本对跨线程调用渲染API的限制更严格——SDL的视频渲染上下文(SDL_Renderer/SDL_Window)并非线程安全,跨线程直接操作这些对象会触发未定义行为,而Ubuntu 22.04的SDL版本可能未严格校验这种操作,导致代码在旧版本正常、新版本异常。

移出线程后主线程被program阻塞,无法处理退出事件,本质是主线程被耗时操作占用,无法轮询SDL事件队列。

正确实现方案

核心思路:用原子布尔变量作为跨线程退出信号,子线程负责耗时的迷宫生成/求解+渲染逻辑(需定期检查退出信号),主线程专注处理事件循环,收到退出指令时触发子线程终止,再完成资源清理。

修改后的完整代码

#include <SDL2/SDL.h>   
#include <array>
#include <cstdlib>    
#include <time.h>
#include <thread>
#include <atomic>
#include "const.hpp"
#include "maze_crea.hpp"
#include "maze_solver.hpp"

// 原子布尔变量,用于跨线程传递退出信号(主线程设置,子线程检查)
std::atomic<bool> g_shouldQuit = false;

// 修改program函数,添加停止标志参数,在耗时操作中定期检查
void program(SDL_Window* pWindow, SDL_Renderer* pRenderer, std::atomic<bool>& shouldQuit){
    std::array<int, MAZE_SIZE<int>> maze;
    std::array<SDL_Rect, MAZE_SIZE<int>> squares;

    for (int i = 0; i < MAZE_SIZE<int>; i++){
        squares[i] = { (i % SIDE_SIZE<int>) * (WIDTHSCREEN<int> / SIDE_SIZE<int>), (i / SIDE_SIZE<int>) * (HEIGHTSCREEN<int> / SIDE_SIZE<int>), WIDTHSCREEN<int> / SIDE_SIZE<int>, HEIGHTSCREEN<int> / SIDE_SIZE<int> };
        if (((i % SIDE_SIZE<int>) % 2 == 0) || ((i / SIDE_SIZE<int>) % 2 == 0)){
            maze[i] = 0;
        } else {
            maze[i] = i;
        }
        // 循环中检查退出信号,避免阻塞过久
        if(shouldQuit.load()) return;
    }

    SDL_SetWindowTitle(pWindow, "Maze Solver (creation)");
    // 假设mazeCrea内部是循环执行的,需要修改它接受停止标志并定期检查
    mazeCrea(pRenderer, maze, squares, shouldQuit);
    if(shouldQuit.load()) return;

    SDL_SetWindowTitle(pWindow, "Maze Solver (solution)");
    // 同理修改mazeSolver,加入停止标志检查
    mazeSolver(pRenderer, maze, squares, shouldQuit);
}

int main(int argc, char* argv[])
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[DEBUG] > %s", SDL_GetError());
        return EXIT_FAILURE;
    }

    SDL_Window* pWindow{ nullptr };
    SDL_Renderer* pRenderer{ nullptr };

    if (SDL_CreateWindowAndRenderer(WIDTHSCREEN<unsigned int>, HEIGHTSCREEN<unsigned int>, SDL_WINDOW_SHOWN, &pWindow, &pRenderer) < 0)
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[DEBUG] > %s", SDL_GetError());
        SDL_Quit();
        return EXIT_FAILURE;
    }

    srand(time(NULL));

    // 启动子线程,传递窗口、渲染器和退出标志
    std::thread Thread(program, pWindow, pRenderer, std::ref(g_shouldQuit));

    SDL_Event events;

    while (true)
    {
        while (SDL_PollEvent(&events))
        {
            switch (events.type)
            {
            case SDL_QUIT:
                // 设置退出信号,通知子线程终止
                g_shouldQuit.store(true);
                // 等待子线程结束,避免资源提前释放
                Thread.join();
                // 清理SDL资源
                SDL_DestroyRenderer(pRenderer);
                SDL_DestroyWindow(pWindow);
                SDL_Quit();
                return EXIT_SUCCESS;
                break;
            }
        }
        // 短暂休眠,减少空循环的CPU占用
        SDL_Delay(10);
    }

    // 正常流程下不会走到这里,保险起见仍加入清理逻辑
    Thread.join();
    SDL_DestroyRenderer(pRenderer);
    SDL_DestroyWindow(pWindow);
    SDL_Quit();

    return EXIT_SUCCESS;
}

关键修改点说明

  • 原子退出信号:使用std::atomic<bool>保证跨线程读写的线程安全性,避免数据竞争。
  • 子线程定期检查:在program的循环、mazeCrea、mazeSolver内部添加退出标志检查,确保子线程能及时响应主线程的终止指令(需同步修改maze_crea.hpp和maze_solver.hpp对应的函数接口,加入停止参数)。
  • 主线程事件循环:启动子线程后立即进入事件循环,收到SDL_QUIT时先设置退出标志,再等待子线程结束,最后清理资源——避免子线程还在操作SDL资源时主线程提前释放,导致崩溃。
  • CPU占用优化:主线程事件循环中加入SDL_Delay(10),减少空循环的CPU消耗。

额外注意事项

  • 如果mazeCrea或mazeSolver是长时间阻塞的函数(无内部循环),需要将其拆分为可中断的步骤,或者使用SDL的线程信号机制,最稳妥的方式是让这些函数支持定期检查退出标志。
  • 严格遵循SDL线程安全规范:SDL_Window和SDL_Renderer的创建/销毁必须在主线程完成,子线程仅可在主线程初始化完成后调用渲染API(部分SDL版本允许,但仍需谨慎),更安全的方式是将渲染逻辑移回主线程,子线程仅传递计算好的迷宫数据。

内容的提问来源于stack exchange,提问作者ISEVEN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 22:50:39