SDL多线程环境下纹理替换同步异常问题求助
多线程下SDL纹理替换显示黑色方块问题排查与解决
问题描述
我尝试将渲染可视化与计算逻辑分离到不同线程,确保计算过程中能实时显示纹理变更,但实现时出现异常:纹理未成功替换为目标纹理,仅显示黑色方块。已尝试使用SDL线程和标准库线程,仍未定位问题原因,以下是复现问题的C语言跳棋测试代码:
#include <stdio.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <pthread.h> #define MAIN_MENU_BACK "Checkers" #define SCREEN_WIDTH 1400 #define SCREEN_HEIGHT 923 typedef struct { char flag; SDL_Rect rect; SDL_Texture *image; } Checker; typedef struct { SDL_Rect rect; SDL_Texture *image; Checker** arrangment; } Board; typedef struct { SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *background; Board* board; SDL_mutex* mutex; } Game; Game* game; bool sdl_initialize(Game *game) { //initializing SDL tools if (SDL_Init(SDL_INIT_EVERYTHING)){ fprintf(stderr, "Error initializing SDL: %s\n", SDL_GetError()); return true; } int img_init = IMG_Init(IMG_INIT_PNG); if ((img_init & IMG_INIT_PNG) != IMG_INIT_PNG){ fprintf(stderr, "Error initializing SDL_IMAGE_PNG: %s\n", IMG_GetError()); return true; } if (TTF_Init()){ fprintf(stderr, "Error initializing SDL_TTF: %s\n", TTF_GetError()); return true; } //create window game->window = SDL_CreateWindow("Checkers", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, 0); if (!game->window){ fprintf(stderr, "Error creating window: %s\n", SDL_GetError()); return true; } //create renderer game->renderer = SDL_CreateRenderer(game->window, -1, 0); if (!game->renderer){ fprintf(stderr, "Error creating renderer: %s\n", SDL_GetError()); return true; } //init background texture game->background = IMG_LoadTexture(game->renderer, "images/background.png"); if (!game->background) { fprintf(stderr, "Error creating Texture: %s\n", IMG_GetError()); return true; } srand((unsigned)time(NULL)); return false; } void out_board(Game* game) { SDL_RenderCopy(game->renderer, game->board->image, NULL, &game->board->rect); for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (game->board->arrangment[i][j].flag == 'r' || game->board->arrangment[i][j].flag == 'w') SDL_RenderCopy(game->renderer, game->board->arrangment[i][j].image, NULL, &game->board->arrangment[i][j].rect); } } } void* SDL_Work(void *ptr) { SDL_Texture* new_texture = IMG_LoadTexture(game->renderer, "images/white_checker.png"); if (new_texture == NULL) { printf("Failed to load texture: %s\n", IMG_GetError()); return NULL; // Exit the thread if the texture fails to load } SDL_LockMutex(game->mutex); if (game->renderer == NULL) { printf("Renderer is not valid!\n"); return 0; } SDL_DestroyTexture(game->board->arrangment[0][1].image); game->board->arrangment[0][1].image = new_texture; if (game->board->arrangment[0][1].image == NULL) { printf("Failed to load texture: %s\n", IMG_GetError()); } SDL_UnlockMutex(game->mutex); } int main() { game = (Game*)malloc(sizeof(Game)); game->window = NULL; game->renderer = NULL; game->background = NULL; game->board = NULL; game->mutex = SDL_CreateMutex(); if (sdl_initialize(game)) { game_cleanup(game, EXIT_FAILURE); } add_board(game); pthread_t thread; pthread_create(&thread, NULL, SDL_Work, NULL); while (true) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: game_cleanup(game, EXIT_SUCCESS); break; default: break; } } SDL_LockMutex(game->mutex); SDL_RenderClear(game->renderer); SDL_RenderCopy(game->renderer, game->background, NULL, NULL); out_board(game); SDL_UnlockMutex(game->mutex); SDL_RenderPresent(game->renderer); SDL_Delay(16); } game_cleanup(game, EXIT_SUCCESS); SDL_DestroyMutex(game->mutex); return 0; }
问题根源
- SDL渲染器非线程安全:SDL的
SDL_Renderer及其关联的纹理操作(IMG_LoadTexture、SDL_DestroyTexture)并非线程安全,所有与渲染器相关的操作必须在创建渲染器的主线程中执行。你在工作线程中直接调用IMG_LoadTexture创建纹理,会导致渲染资源状态混乱,最终显示黑色方块。 - 纹理生命周期冲突:工作线程销毁旧纹理时,主线程可能正在使用该纹理进行渲染,触发未定义行为,导致渲染异常。
- 同步逻辑覆盖不全:当前互斥锁仅保护了纹理指针的替换,但未解决跨线程操作渲染资源的核心问题。
解决方案
核心思路
工作线程仅负责计算逻辑,不直接操作SDL渲染资源;通过线程安全的消息队列通知主线程,由主线程完成纹理的加载、替换和销毁,严格遵循SDL的线程使用规则。
修正后的代码示例
1. 添加消息队列相关定义
// 线程消息结构体,用于传递纹理更新请求 typedef struct { int type; // 消息类型:0表示更新纹理 int row; // 目标棋子行 int col; // 目标棋子列 const char* path; // 纹理路径 } ThreadMsg; #define MSG_QUEUE_SIZE 10 ThreadMsg msg_queue[MSG_QUEUE_SIZE]; int queue_head = 0; int queue_tail = 0; SDL_mutex* queue_mutex; // 消息队列互斥锁 SDL_cond* queue_cond; // 消息队列条件变量
2. 修改工作线程逻辑
工作线程不再操作纹理,仅发送更新请求:
void* SDL_Work(void *ptr) { // 模拟计算完成后,发送纹理更新请求 ThreadMsg update_msg = { .type = 0, .row = 0, .col = 1, .path = "images/white_checker.png" }; // 写入消息队列 SDL_LockMutex(queue_mutex); msg_queue[queue_tail] = update_msg; queue_tail = (queue_tail + 1) % MSG_QUEUE_SIZE; SDL_CondSignal(queue_cond); // 通知主线程有新消息 SDL_UnlockMutex(queue_mutex); return NULL; }
3. 修改主线程逻辑
主线程负责处理消息队列中的请求,完成纹理更新:
int main() { game = (Game*)malloc(sizeof(Game)); game->window = NULL; game->renderer = NULL; game->background = NULL; game->board = NULL; game->mutex = SDL_CreateMutex(); // 初始化消息队列同步对象 queue_mutex = SDL_CreateMutex(); queue_cond = SDL_CreateCond(); if (sdl_initialize(game)) { game_cleanup(game, EXIT_FAILURE); } add_board(game); pthread_t thread; pthread_create(&thread, NULL, SDL_Work, NULL); while (true) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: game_cleanup(game, EXIT_SUCCESS); break; default: break; } } // 处理线程消息队列 SDL_LockMutex(queue_mutex); while (queue_head != queue_tail) { ThreadMsg curr_msg = msg_queue[queue_head]; queue_head = (queue_head + 1) % MSG_QUEUE_SIZE; SDL_UnlockMutex(queue_mutex); // 处理纹理更新请求 if (curr_msg.type == 0) { SDL_Texture* new_tex = IMG_LoadTexture(game->renderer, curr_msg.path); if (new_tex != NULL) { SDL_LockMutex(game->mutex); // 销毁旧纹理,替换为新纹理 SDL_DestroyTexture(game->board->arrangment[curr_msg.row][curr_msg.col].image); game->board->arrangment[curr_msg.row][curr_msg.col].image = new_tex; SDL_UnlockMutex(game->mutex); } else { fprintf(stderr, "Failed to load texture: %s\n", IMG_GetError()); } } SDL_LockMutex(queue_mutex); } SDL_UnlockMutex(queue_mutex); // 渲染流程 SDL_LockMutex(game->mutex); SDL_RenderClear(game->renderer); SDL_RenderCopy(game->renderer, game->background, NULL, NULL); out_board(game); SDL_UnlockMutex(game->mutex); SDL_RenderPresent(game->renderer); SDL_Delay(16); } // 清理资源 SDL_DestroyCond(queue_cond); SDL_DestroyMutex(queue_mutex); SDL_DestroyMutex(game->mutex); game_cleanup(game, EXIT_SUCCESS); return 0; }
关键注意事项
- 所有SDL渲染相关API(纹理创建、销毁、渲染拷贝)必须在主线程调用,这是SDL的硬性规则。
- 使用互斥锁保护共享数据(如棋盘纹理指针)的读写,避免数据竞争。
- 通过条件变量+消息队列实现线程间安全通信,确保工作线程的更新请求能被主线程及时处理。
内容的提问来源于stack exchange,提问作者Kasatka
相关产品推荐
相关产品推荐

