Search code examples
c++sdlsdl-2

SDL2: Sometimes make segement fault while Rendering and Event Polling in the different thread


So in my project I make a Event thread to catch the sdl event and may be pass it to the main thread to rander. But sometimes I get the Segementfault. this is my test code.

#include "SDL2/SDL.h"

#include <SDL2/SDL_timer.h>
#include <iostream>
#include <thread>

using namespace std;

int main() {
    SDL_Init(SDL_INIT_EVERYTHING);

    bool running = true;
    /* this is my event thread */
    auto run = [&] {
        for (; running; ) {
            SDL_Event event; SDL_PollEvent(&event);
            // ...
            SDL_Delay(10);
        }
    };
    thread t(run);

    /* and the main is the randering thread */
    for (int i = 0; i < 10; ++i) {
        SDL_Window *window = SDL_CreateWindow("cycle. ", 
                                            100,
                                            100,
                                            600,
                                            600,
                                            SDL_WINDOW_SHOWN);

        SDL_Renderer *screen = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
        
        // ... do some rander ...
        // may be i will get some task to rander use the blocking queue

        SDL_Delay(500);
        SDL_DestroyRenderer(screen);
        SDL_DestroyWindow(window);

    }

    running = false;
    t.join();

    SDL_Quit();
}
  • I want to know why I get the segement fault.
  • Should I make the Event listening and the rander in the same thead? (This will cause another problem: If placed in the same thread, when I set the loop to only 60 times per second, it also means that I can only render 60 times per second. This will cause my program to become stuck.)

Solution

  • Fundamentally, you should try to call SDL from a single thread. Even if you decide you need to multithread your program, you should do work in other threads and then synchronize that work to the main thread, which should use SDL to render / event-handle / etc.

    Your program may be segfaulting because you're attempting to join() a thread that already join()-ed, so you want to at least check joinable() before you do so.

    You also need to call PollEvent in a loop, since multiple events may be queued here.

    But, especially since you're using Delays, it seems like you won't need the possible performance gained by multithreading your events. I would therefore suggest something like this:

    #include <SDL2/SDL.h>
    
    #include <iostream>
    #include <thread>
    
    using namespace std;
    
    int main() {
        SDL_Init(SDL_INIT_EVERYTHING);
    
        bool running = true;
    
        SDL_Window *window =
            SDL_CreateWindow("cycle. ", 100, 100, 600, 600, SDL_WINDOW_SHOWN);
    
        SDL_Renderer *screen =
            SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    
        /* and the main is the randering thread */
        while (running) {
            SDL_Event event;
            while (SDL_PollEvent(&event)) {
                // TODO: handle events
                // for example, set `running` to false when the window is closed or
                // escape is pressed
                // ...
            }
    
            // TODO: render here
            // ...
        }
    
        SDL_DestroyRenderer(screen);
        SDL_DestroyWindow(window);
    
        SDL_Quit();
    }
    

    I'm not going to assume what you do and don't need, so here's a possible solution where you multithread the events (but you really, really, really should only use this if you absolutely need it).

    This does the same as your code, except it calls PollEvent in the same thread as the rendering (the main thread), but handles the events somewhere else.

    For this we use a queue of events, and a mutex to make sure it's thread-safe. I'm just putting this here for completeness' sake, and you probably (definitely) don't need this.

    #include <SDL2/SDL.h>
    
    #include <iostream>
    #include <mutex>
    #include <queue>
    #include <thread>
    
    using namespace std;
    
    int main() {
        SDL_Init(SDL_INIT_EVERYTHING);
    
        bool running = true;
    
        std::mutex eventQueueMutex;
        std::queue<SDL_Event> eventQueue;
        auto handleEvents = [&running, &eventQueue, &eventQueueMutex] {
            while (running) {
                {  // scope for unique_lock
                    std::unique_lock<std::mutex> lock(eventQueueMutex);
                    if (!eventQueue.empty()) {
                        auto event = eventQueue.front();
                        eventQueue.pop();
                        lock.unlock();
                        // TODO: handle the event here
                    }
                }  // end of scope for unique_lock
                std::this_thread::sleep_for(
                    std::chrono::milliseconds(16));  // adjust this to your needs
            }
        };
    
        std::thread eventHandlerThread = std::thread(handleEvents);
    
        SDL_Window *window =
            SDL_CreateWindow("cycle. ", 100, 100, 600, 600, SDL_WINDOW_SHOWN);
    
        SDL_Renderer *screen =
            SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    
        /* and the main is the randering thread */
        while (running) {
            SDL_Event event;
            eventQueueMutex.lock();
            while (SDL_PollEvent(&event)) {
                eventQueue.push(event);  // copy event into queue
            }
            eventQueueMutex.unlock();
    
            // TODO: render here
            // ...
        }
    
        if (eventHandlerThread.joinable()) {
            eventHandlerThread.join();
        }
    
        SDL_DestroyRenderer(screen);
        SDL_DestroyWindow(window);
    
        SDL_Quit();
    }
    

    In both of these examples, check for my TODO comments to see where you write your code.