Dlaczego wywołanie w wątku SDL_GL_MakeCurrent() powoduje mem leak?

Odpowiedz Nowy wątek
2019-08-01 13:48
0

Tworzę 2 nowe wątki i 2 konteksty, po jednym na wątek, plus kontekst główny. Wywołanie SDL_GL_MakeCurrent(window, XXXcontext) powoduje memleak. Problem znika gdy używamy tej funkcji w głównym wątku, ale przecież tutaj chodzi o to, żeby było kilka wątków. Również nie ma problemu gdy SDL_GL_MakeCurrent(window, XXXcontext) jest komentarzem, a nie wywołaniem.
Dlaczego tak się dzieje?

ustawienia okna

SDL_GLContext mainContext = nullptr;
SDL_GLContext thread1Context = nullptr;
SDL_GLContext thread2Context = nullptr;

SDL_Window* window = nullptr;

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
//--------------------
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
//--------------------
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
window = SDL_CreateWindow("window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 900, SDL_WINDOW_OPENGL);

thread1Context = SDL_GL_CreateContext(window);
thread2Context = SDL_GL_CreateContext(window);
mainContext = SDL_GL_CreateContext(window);

//glew
InitOpengl();

wersja pętli z memleakiem

void MainLoop() 
{
    while (!quit)
    {
        thread1 = std::thread([&]() {
            SDL_GL_MakeCurrent(window, thread1Context);

            //SDL_GL_MakeCurrent(window, nullptr);
        }); 

        thread2 = std::thread([&]() {
            SDL_GL_MakeCurrent(window, thread2Context);

            //SDL_GL_MakeCurrent(window, nullptr);
        });

        thread1.join();
        thread2.join();
    }

    SDL_GL_DeleteContext(thread1Context);
    SDL_GL_DeleteContext(thread2Context);
    SDL_GL_DeleteContext(mainContext);

    SDL_DestroyWindow(window);
}

i bez memleaku ale też bez wątków

void MainLoop() 
{
    while (!quit)
    {
        //thread1 = std::thread([&]() {
            SDL_GL_MakeCurrent(window, thread1Context);

            //SDL_GL_MakeCurrent(window, nullptr);
        //});   

        //thread2 = std::thread([&]() {
            SDL_GL_MakeCurrent(window, thread2Context);

            //SDL_GL_MakeCurrent(window, nullptr);
        //});

        //thread1.join();
        //thread2.join();
    }

    SDL_GL_DeleteContext(thread1Context);
    SDL_GL_DeleteContext(thread2Context);
    SDL_GL_DeleteContext(mainContext);

    SDL_DestroyWindow(window);
}
edytowany 2x, ostatnio: JarekBisnesu, 2019-08-01 13:49

Pozostało 580 znaków

2019-08-01 13:56
1

A czy w dokumentacji SDL jest coś na temat gwarancji bezpieczeństwa używania API wielowątkowo?
Jeśli ta biblioteka ma jakiś ukryty stan bez synchronizacji to "sorry taki mamy klimat".
To jest jeden z powodów dlaczego nie powinno się używać zmiennych globalnych.

FAQDevelopment - SDL Wiki'

Can I call SDL video functions from multiple threads?

No, most graphics back ends are not thread-safe, so you should only call SDL video functions from the main thread of your application.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22, 2019-08-01 13:57
To nie są zmienne globalne, tutaj jest to tylko minimalny przykład. A nawet jeśli byłyby to zmienne globalne to nigdzie indziej ani nie korzystam z nich ani nie ma konfliktu nazw. Plus wszystkie strony pokazujące w jaki sposób korzystać z SDL i opengl w wieluwątkach nie korzystają z żadnej synchronizacji. Przynajmniej ja na taką nie trafiłem, a widziałem wiele. - JarekBisnesu 2019-08-01 13:59
ale to nie u ciebie, ale w SDL. Zresztą to nieważne FAQ wyraźnie mówi, że to jest organicznie API systemowego użytego przez SDL. - MarekR22 2019-08-01 14:09
FAQ mówi o funkcjach od SDL, nie od OpenGL. Chyba, że coś źle rozumiem. https://gamedev.stackexchange[...]lti-threading-for-rhythm-game he SDL2 event/render system is not yet thread-safe, ..., but you can overcome this limitation by using OpenGL - JarekBisnesu 2019-08-01 14:25
To teraz wytłumacz mi czemu uważasz, że SDL_GL_MakeCurrent nie kwalifikuje się jako SDL video functions? - MarekR22 2019-08-01 14:55
Rzeczywiście wygląda na to, że jednak jest to funkcja video. Mogę w jakiś sposób zrobić to bezpieczne dla wielowątkowości? Próbowałem użyć mutexów ale niczego to nie zmieniło. Trochę to dziwne dla mnie, bo na wielu stronach jest napisane, że właśnie w taki sposób powinienem zrobić, a nie wspominają nic o tym, że nie jest to bezpieczne. Wydaje mi się, że ustawienie SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); powinno wszystko załatwić. Dziwne jest też to, że w trochę innym przypadku już tego memleaku nie ma. - JarekBisnesu 2019-08-01 17:12
Nie wiem dlaczego nie moge edytować postu, dlatego napisze odpowiedź włączając ten kod (nie ma wycieku). - JarekBisnesu 2019-08-01 17:12

Pozostało 580 znaków

2019-08-01 17:12
0

        std::thread thread;
        SDL_GLContext mainContext = nullptr;
    SDL_GLContext sharedContext = nullptr;

    SDL_Window* window = nullptr;

    App()
    {
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
        SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
        window = SDL_CreateWindow("window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 900, SDL_WINDOW_OPENGL);

        sharedContext = SDL_GL_CreateContext(window);
        mainContext = SDL_GL_CreateContext(window);

        InitOpengl();
       }

       ~App()
       {
           thread.join();
       }

    void App::ThreadLoop()
    {
        thread = std::thread([&]() {
            while (!quit) {
                SDL_GL_MakeCurrent(window, sharedContext);

                SDL_GL_MakeCurrent(window, nullptr);
            }
        });
    }

    void App::MainLoop() override
    {
        ThreadLoop();
        while (!quit)
        {
            while (SDL_PollEvent(&event)) {
                if (event.type == SDL_QUIT) {
                    quit = true;
                }
            }

                        SDL_GL_MakeCurrent(window, mainContext);
                    SDL_GL_SwapWindow(window);
        }

        SDL_GL_DeleteContext(sharedContext);
        SDL_GL_DeleteContext(mainContext);

        SDL_DestroyWindow(window);
    }

I tu nie ma wycieku nawet jak SDL_GL_MakeCurrent() jest w innym wątku, ale jak już bym chciał w tym wątku stworzyć kolejny wątek np.


void ThreadLoop()
    {
        thread = std::thread([&]() {
            while (!quit) {
                std::thread thread2([&]() {
                    SDL_GL_MakeCurrent(window, sharedContext);

                    SDL_GL_MakeCurrent(window, nullptr);        
                });
                thread2.join();
            }
        });
    }

to wyciek znów jest.

Pewnie masz rację z tą synchronizacją. Ale w takim wypadku w jaki sposób mogę to naprawić? Próbowałem już mutexem, nadal to samo, więc no...

Pozostało 580 znaków

2019-08-02 09:41
0

Przyjrzałbym się tym trzem linijkom kodu:

 thread1Context = SDL_GL_CreateContext(window);
 thread2Context = SDL_GL_CreateContext(window); 
 mainContext = SDL_GL_CreateContext(window);

Czy przypadkiem nie jest tak, że zwracane są tutaj wskaźniki do tego samego kontekstu?

Nie, SDL_GL_CreateContext tworzy nowy kontekst Opengl i go zwraca. Tzn. cały czas dostajesz inny kontekst. - JarekBisnesu 2019-08-02 11:49

Pozostało 580 znaków

2019-08-02 09:47
0

Żeby móc naprawić najpierw trzeba wiedzieć co naprawić. Jeżeli nie wiesz gdzie i co cieknie, to masz ręce związane. Gdb twoim przyjacielem.

Cieknie na pewno z SDL_GL_MakeCurrent, ale dziwne to jest, że jak mam 2 pętle działające w tym samym czasie i wtedy dam SDL_GL_MakeCurrent to nie cieknie, ale jak już w tej pętli jest nowy wątek, to SDL_GL_MakeCurrent cieknie. - JarekBisnesu 2019-08-02 11:59

Pozostało 580 znaków

2019-08-02 11:18
0

A może lepiej wyjaśnij po co ci wątki?
Jestem pewien, że ludzie pisali dużo bardziej skomplikowane gry bez użycia wątków na dużo słabsze maszyny i nic im się nie zacinało.
Ja wiem, że teraz jest moda na wielowątkowość, bo procesory mają coraz więcej rdzeni, ale programowanie wielowątkowe jest o wiele bardziej trudne od zwykłego programowania, szczególnie jeśli ktoś jeszcze dobrze nie opanował programowania.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.

Pozostało 580 znaków

2019-08-02 11:55
0
MarekR22 napisał(a):

A może lepiej wyjaśnij po co ci wątki?

Żeby renderować grafikę w tym samym momencie co, np. poruszanie oknem, żeby apka się nie zacinała. Żeby nauczyć się wielowątkowości .
Żeby ładować assety i w tym samym momencie pokazać na ekranie pasek postępu, a nie czarny ekran i brak możliwości jakiejkolwiek interakcji.
Czy nie ładniej to będzie wyglądać?

Jestem pewien, że ludzie pisali dużo bardziej skomplikowane gry bez użycia wątków na dużo słabsze maszyny i nic im się nie zacinało.
Ja wiem, że teraz jest moda na wielowątkowość, bo procesory mają coraz więcej rdzeni, ale programowanie wielowątkowe jest o wiele bardziej trudne od zwykłego programowania, szczególnie jeśli ktoś jeszcze dobrze nie opanował programowania.

Umiem napisać kod na jednym wątku bez żadnych problemów, dlatego teraz chciałbym się zająć wielowątkowością.

Pozostało 580 znaków

2019-08-02 12:05
0

Co do przesuwania okna: https://stackoverflow.com/a/27195881/1387438
Co zacinek: to bierzesz profiler (ten MSVS jest dość przyjemny) ustalasz gdzie i dlaczego kod się przycina i rozwiązujesz problem tam. Skill bardziej cenny niż wpychanie wielowątkowości na siłę.

Co do uczenia się wielowątkowości: polecam unikać jak ognia dopóki w normalnym programowaniu nie poczujesz się pewnie.
Największy problem z wielowątkowością: możesz stworzyć kod, który będzie ci się wydawał prawidłowy, będzie działać zawsze u ciebie, a innych będzie się wieszał lub crashował co 5 minut. Programowanie wątków wymaga o wiele większej dyscypliny podczas pisania kodu.

Większość kursów wielowątkowości demonstruje ją na zbyt mało wymagających problemach, przez co efektywność kodu jest mniejsza, a u ucznia wyrabia się nawyk wpychania wątków do każdego głupiego zadania.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22, 2019-08-02 12:07

Pozostało 580 znaków

2019-08-02 12:36

Co do przesuwania okna: https://stackoverflow.com/a/27195881/1387438
Co zacinek: to bierzesz profiler (ten MSVS jest dość przyjemny) ustalasz gdzie i dlaczego kod się przycina i rozwiązujesz problem tam. Skill bardziej cenny niż wpychanie wielowątkowości na siłę.

Nie chodzi mi tu o ścinki, że o lagi, że za mało FPS, czy ms., a z profilera też umiem korzystać i to robię. Chodzi mi o to, że jeśli grafika jest renderowana, np. katscenka, i w tym momencie złapie pasek przesuwania okna (ten standardowy, a nie jakiś stworzony przez nasze gui), to grafika katscenka stanie w miejscu, bo korzystamy z eventów okna. Sprawdzę link który podałeś jak wrócę z pracy.

Co do uczenia się wielowątkowości: polecam unikać jak ognia dopóki w normalnym programowaniu nie poczujesz się pewnie.
Największy problem z wielowątkowością: możesz stworzyć kod, który będzie ci się wydawał prawidłowy, będzie działać zawsze u ciebie, a innych będzie się wieszał lub crashował co 5 minut. Programowanie wątków wymaga o wiele większej dyscypliny podczas pisania kodu.

Większość kursów wielowątkowości demonstruje ją na zbyt mało wymagających problemach, przez co efektywność kodu jest mniejsza, a u ucznia wyrabia się nawyk wpychania wątków do każdego głupiego zadania.

Czuje się aż za pewnie w normalnym programowaniu (jako programowaniu, bo czasami problem nie tkwi w programowaniu a np. w matematyce). Raczej nie korzystam z kursów, wole rozmowy na gdc czy cppcon i nie wpycham wszędzie wielowątkowości ale akurat renderowanie wielowątkowe jest ważnym tematem moim zdaniem,

edytowany 2x, ostatnio: JarekBisnesu, 2019-08-02 12:36
Pokaż pozostałe 12 komentarzy
się nie załaduje - JarekBisnesu 2019-08-02 22:13
Chodzi o to, że operacje blokujące zajmują czas, który można przeznaczyć na coś innego. Można skorzystać w operacji nieblokujących, które zazwyczaj są dostarczone przez biblioteki systemowe, lub używać operacji blokujących w osobnym wątku, co sprawi, że tylko ten osobny wątek będzie spał czekając na dane, podczas gdy inne wątek będzie robił swoją robotę. Ale samo "zarządzanie" wątkami z poziomu systemu nie jest tanie. Nie jest to kwestia mocy obliczeniowej, a wywołań systemowych, operujących na "plikach" i we/wyjściu, które w podstawowej formie są blokujące. - nalik 2019-08-02 22:27
Kojarzę coś. Wiem o czym mówisz. Oglądałem jakieś przemówienie, to chyba było o lock-free queue, mówiący pokazywał przykład z autostradą, z blokowaniem było chyba, że są światła na drodze, a bez blokowania, że zamiast świateł jest jedna droga niżej, druga wyżej i nic się nie blokuje. - JarekBisnesu 2019-08-02 22:35
Jest to kwestia zmarnowanych cykli procesora, które można była przeznaczyć na coś innego. Oraz niepotrzebnego opóźnienia innych operacji, które nie zależą od tej operacji blokujących. Ale tak jak wspomniałem, można skorzystać z operacji nieblokujących/asynchronicznych. Pod spodem system operacyjny przyjmuje operacje odczytu i kolejkuje do sterownika, a potem firmware urządzenia, które działa niezależnie od CPU (np. dysk twardy). Dane, które są ładowane nie muszą, a nawet nie powinny, przechodzić przez CPU, tylko trafić od razu w odpowiednie miejsce w pamięci. - nalik 2019-08-02 22:43
Operacja blokująca zatrzyma cały wątek, dopóki dane nie zostaną załadowane. Operacja nieblokująca oferuje inne api, które umożliwia operowanie bez blokowania, np przez sprawdzenie statusu, albo wywołanie zwrotne. Same wątki w przypadku operacji, które są "IO bound", to po prostu uproszczenie modelu programistycznego, kosztem wydajności ( co innego w przypadku operacji CPU bound, czyli kosztownych obliczeniowo, wtedy dodatkowy rdzeń procesora, ma którym działa inny wątek, potrafi zwiększyć wydajność obliczeń, o ile jest poprawnie użyty). - nalik 2019-08-02 22:45

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0