std::lock_guard (mutex) produziert deadlock
-
Hey!
Also da ich immer noch neu in diesem Thema bin, sage ich einfach vielleicht mal, was mein Ziel ist. Nehmen wir mal die Funktion modelGetDirection(), die ja oben auch definiert ist, und angenommen ich habe 4 Threads laufen.
Jeder Thread ruft modelGetDirection() jeweils unterschiedlich oft auf, völlig egal wie oft, dann möchte ich aber, dass immer nur ein thread, meinetwegen thread nr 2 aktuell, modelGetDirection() aufruft. Die anderen threads sollen warten bis thread nr 2 fertig ist. Dann sollen die anderen threads sich meinetwegen drum kloppen wer als nächstes dran ist. Aber es ist immer nur ein thread dran mit dem Ausführen dieser Funktion.
Ist hier dann aber std::mutex_recursive die richtige Wahl? Ist das so zu verstehen, dass std::mutex_recursive.lock() quasi jedes Mal gestapelt wird und wieder vom Stack entfernt wird, wenn unlock() meinetwegen durch einen lock_guard aufgerufen wird? Ich bin mittlerweile leicht verwirrt...
-
Um mir eine Engine + ein Spiel runter zu laden, zu kompilieren und zu debuggen fehlt mir im Moment die Zeit.
Wenn die Funktion in dem Beispiel Programm funktioniert, richt es fast, als ob du in dem Spiel die Engin nicht richtig initialisiert hast. Sollte dann aber nicht unbdeingt mit dem Mutex zusammen hängen. Aber bei UB kann's halt irgendwo krachen.Ich würde mal im Debugger durch steppen und mir die Werte der einzelnen Member Variablen z.B. anschauen.
Wie wird level1ControlShip() denn aufgerufen. Da hast du ja so ein ekelhaften Void Pointer drin. Das ist ein Konstrukt, dass ich früher mal in der C - Netzwerkprogrammierung gebraucht habe, aber in C++ noch nie.
Ich habe grade mal in 2 source files der Engine geschaut. Meiner Meinung nach würden der ein paar moderne C++ Konstrukte auch ganz gut tun, aber das ist ja hier nicht das Thema.
-
@lynarstudios
ein Thread kann std::mutex nur einmal locken. Wenn dann ein anderer Thread den Lock haben will muss er warten. Wenn die Sachen abgearbeitet sind, wird der lock entfernt (sonst deadlock).Ein Thread kann eine mutex aber nicht 2 mal nacheinander locken ohne das der erste Lock im Programmablauf vorher aufgehoben wurde. Wenn man das doch benötigt, z.B. um eine Mutex in einer rekursiven Funktion aufzurufen, benötigt man std::mutex_recursive.
Dein Lockguard in modelGetDirection() wird nach abarbeiten der Funktion wieder zerstört und der Lock Freigegeben. Du darfst halt in den, von modelGetDirection() aufgerufenen Funktionen, die Mutex "this->mtxModel.modelGetDirection" nicht nochmal verwenden.
-
Hier noch ein Beispiel zu den Erklärungen von @Schlangenmensch:
Im untenstehend Code wird in
Foo::say_hello
das 1. Mal gelockt, dann wird inFoo::name
das 2. Mal versucht den Mutex zu locken, obwohl er schon gelockt ist (alles auf ein und demselben Thread). Das nur dann, wenn der Mutex "rekursiv" aufrufbar ist, im Falle vonstd::mutex
blockiert das Programm.#include <iostream> #include <string> #include <thread> #include <mutex> template <typename Mutex> struct Foo { void say_hello() { std::lock_guard<Mutex> lock{ m_mutex }; std::cout << "Hello " << name() << "\n"; } std::string name() { std::lock_guard<Mutex> lock{ m_mutex }; return "lynarstudios"; } private: Mutex m_mutex; }; int main() { std::thread thread{ [] { ///Foo<std::mutex> foo{}; Foo<std::recursive_mutex> foo{}; foo.say_hello(); }}; thread.join(); }
-
Meine erste Vermutung war diese Zeile hier:
Parameter param = (Parameter) pointer;
Weil pointer ja ein Pointer ist, hätte ich hier bei Parameter einen Stern erwartet. Aber vermutlich ist das ein Pointer-typedef, denn es wird ja auf param->... zugegriffen. Warum dann nicht als Parameter direkt "Parameter" statt void* nehmen?
Ich würde aber sehr davon abraten, den * irgendwie zu verstecken.
Und dann frage ich mich noch, ob du speedLeft noch anderswo nutzt. Denn irgendwie erscheint es komisch, dass das jedes mal berechnet wird, die statische Konstante INCREASE_SPEED_LEFT aber dann vom Resultat gesetzt wird.
-
Hey!
Der Parameter pointer ist etwas kompliziert zu erklären. Das hängt allerdings mit dem modularen Aufbau zusammen, der das zumindest bis jetzt leider notwendig macht. Dieser Parameter Pointer enthält Zeiger auf die engine, auf die game class und auf eine memory organizer Klasse. Diesen Zeiger gibt es halt, weil die Headereinbindungen sonst nicht funktionieren würden, weshalb er auch vom Typ void* als Parametertyp erst einmal sein muss.
speedLeft wird bei jedem Funktionsaufruf von level1ControlShip ausgelesen, wird aber nicht verändert. speedLeft wird in diversen Abfragen im Folgecode verwendet.
-
@lynarstudios sagte in std::lock_guard (mutex) produziert deadlock:
Diesen Zeiger gibt es halt, weil die Headereinbindungen sonst nicht funktionieren würden, weshalb er auch vom Typ void* als Parametertyp erst einmal sein muss.
. o O ( das riecht nach Quatsch mit Sauce )
-
Dann ist ja meine Herangehensweise eigentlich richtig:
Egal wie viele Threads es gibt. Geht irgendein Thread in diese Funktion rein, soll die Funktion für andere Threads blockiert werden, solange sie abgearbeitet wird.
Daher sollte
lock_guard<mutex> lockA(this->mtxModel.modelGetDirection);
Die richtige Herangehensweise sein, oder?
Ich würde mal im Debugger durch steppen und mir die Werte der einzelnen Member Variablen z.B. anschauen.
Habe ich gemacht. Mir sind keine fehlerhaften Werte aufgefallen:
-
this->mtxModel.mtxModelGetDirection {...} std::mutex
-
std::_Mutex_base {_Mtx_storage={_Val=8.487983163861e-314#DEN _Pad=0x00007ff9190aea28 "" } } std::_Mutex_base
-
_Mtx_storage {_Val=8.487983163861e-314#DEN _Pad=0x00007ff9190aea28 "" } std::_Align_type<double,80> _Val 8.487983163861e-314#DEN double
-
_Pad 0x00007ff9190aea28 "" char[80] [0] 0 '\0' char [1] 0 '\0' char [2] 0 '\0' char [3] 0 '\0' char [4] 4 '\x4' char [5] 0 '\0' char [6] 0 '\0' char [7] 0 '\0' char [8] 24 '\x18' char [9] -1 'ÿ' char [10] 10 '\n' char [11] 0 '\0' char [12] 32 ' ' char [13] 0 '\0' char [14] 0 '\0' char [15] 0 '\0' char [16] 0 '\0' char [17] 0 '\0' char [18] 0 '\0' char [19] 0 '\0' char [20] 1 '\x1' char [21] 0 '\0' char [22] 0 '\0' char [23] 0 '\0' char [24] 34 '\"' char [25] 5 '\x5' char [26] -109 '“' char [27] 25 '\x19' char [28] 3 '\x3' char [29] 0 '\0' char [30] 0 '\0' char [31] 0 '\0' char [32] 88 'X' char [33] -1 'ÿ' char [34] 10 '\n' char [35] 0 '\0' char [36] 0 '\0' char [37] 0 '\0' char [38] 0 '\0' char [39] 0 '\0' char [40] 0 '\0' char [41] 0 '\0' char [42] 0 '\0' char [43] 0 '\0' char [44] 7 '\a' char [45] 0 '\0' char [46] 0 '\0' char [47] 0 '\0' char [48] 112 'p' char [49] -1 'ÿ' char [50] 10 '\n' char [51] 0 '\0' char [52] 80 'P' char [53] 0 '\0' char [54] 0 '\0' char [55] 0 '\0' char [56] 0 '\0' char [57] 0 '\0' char [58] 0 '\0' char [59] 0 '\0' char [60] 1 '\x1' char [61] 0 '\0' char [62] 0 '\0' char [63] 0 '\0' char [64] 34 '\"' char [65] 5 '\x5' char [66] -109 '“' char [67] 25 '\x19' char [68] 1 '\x1' char [69] 0 '\0' char [70] 0 '\0' char [71] 0 '\0' char [72] -48 'Ð' char [73] -1 'ÿ' char [74] 10 '\n' char [75] 0 '\0' char [76] 0 '\0' char [77] 0 '\0' char [78] 0 '\0' char [79] 0 '\0' char
Ich habe grade mal in 2 source files der Engine geschaut. Meiner Meinung nach würden der ein paar moderne C++ Konstrukte auch ganz gut tun, aber das ist ja hier nicht das Thema.
Für so etwas bin ich immer offen. Ich stehe nur leider etwas unter Zeitdruck und sollte das Problem zuerst angehen.
Wie wird level1ControlShip() denn aufgerufen.
param->game->level1ControlShip() ist der Funktionsaufruf in einer übergeordneten Funktion. Wenn ich übrigens
param->engine->modelGetDirection(MODEL_VERA, LEFT);
in level1ControlShip() auskommentiere, gibt es dort natürlich keinen Fehler. Darunter kommt dann aber irgendwann die Zeile:
param->engine->modelGetPositionD(MODEL_VERA);
Diese Funktion spinnt ebenfalls an der gleichen Stelle, nämlich beim Lock Guard:
glm::vec2 LEMoon::modelGetPositionD(uint32_t id) { lock_guard<mutex> lockA(this->mtxModel.modelGetPositionD); glm::vec2 position; LEModel * pElem = this->modelGet(id); if(pElem == nullptr) {pElem = this->modelGetFromBuffer(id);} if(pElem != nullptr) {position = pElem->pModel->mdlGetPositionD();} else { #ifdef LE_DEBUG char * pErrorString = new char[256 + 1]; sprintf(pErrorString, "LEMoon::modelGetPositionD(%u)\n\n", id); this->printErrorDialog(LE_MDL_NOEXIST, pErrorString); delete [] pErrorString; #endif } return position; }
Das hatte ich vorher nicht bemerkt.
-
-
Cocktail oder Barbecue Sauce?
-
Zeige mal die Definition von
Parameter
und wie die FunktionGame::level1ControlShip
aufgerufen wird.
-
@lynarstudios sagte in std::lock_guard (mutex) produziert deadlock:
Cocktail oder Barbecue Sauce?
missing forward-declaration-sauce.
-
Generell sehe ich kein Fehler in deiner Verwendung von der Mutex, nach wie vor glaube ich, dass mirgendwas nicht initialisiert ist.
Was spricht denn gegen:
void Game::level1ControlShip(Parameter param, bool controlAble) { ... glm::vec2 speedLeft = param->engine->modelGetDirection(MODEL_VERA, LEFT); ... }
dein Parameter wird offenbar irgendwo ander definiert und ist ein Typedef auf ein Pointer, was recht verwirrend ist. Aber den Typ solltest du auch in der Funktionsdefinition verwenden können.
Funktioniert der Code denn Singlethreaded?
P.S: Gibt es einen Grund dafür, dass du konstant C-Strings verwendest, anstelle von std::string?
-
Definition von Parameter:
typedef struct sParameter { MemoryOrganizer memoryOrganizer; LEMoonInstance engine; GameInstance game; } *Parameter;
Aufruf von Game::stageAsteroidFlight:
#include "../../config.h" #include "../../parameter.h" #include "../../res.h" #include "../../include_level1/res_prolog.h" #include "../../include_level1/res_intro_flight.h" #include "../../include_level1/res_asteroid_flight.h" #include "../../include_level1/res_satellite.h" #include "../../include_level1/level1_intro_flight.h" #include "../../include_level1/level1_asteroid_flight.h" void Game::stageAsteroidFlight(void * pointer) { Parameter param = (Parameter) pointer; SDL_Point position; SDL_Point size; this->menuDisplayFPS(param); level1DisplaySpaceParticles(param); this->level1HandleShip(param); this->level1HandleAsteroids(param); // skip #ifdef BUILD_MOBILE if(this->windowGet(WINDOW_SKIP)->pWindow->visible(param->engine)) { this->effectGet(EFFECT_TEXT_SKIP)->pEffect->effectTextGlint(param->engine, WINDOW_SKIP_TEXT, 150.0f, 255.0f, 100.0f); if(this->windowGet(WINDOW_SKIP)->pWindow->buttonReleased(param->engine, WINDOW_SKIP_BUTTON)) { level1SkipAsteroidFlight(param); param->engine->modelSetTextureActive(MODEL_VERA, TEXTURE_DEFAULT, LE_FALSE); param->engine->textSetVisible(TEXT_DISTANCE, LE_FALSE); param->engine->textSetVisible(TEXT_DISTANCE_NUMBER, LE_FALSE); param->engine->textSetVisible(TEXT_START, LE_FALSE); size = param->engine->modelGetSize(MODEL_SATELLITE); position = param->engine->modelGetPosition(MODEL_SATELLITE); param->engine->modelSetPosition(MODEL_SATELLITE, position.x, param->engine->getScreenHeight() - (360.0f / 160.0f) * size.y); level1ResetParticles(param); } } #endif #ifdef SLL_DEBUG this->level1UpdateCollBoxes(param->engine); if(!this->level1ShowCollisionBoxesFlag() && param->engine->keyEvent(SDL_KEYUP, SDLK_a)) { this->level1SetCollBoxesVisible(param->engine, LE_TRUE); this->level1SetShowCollisionBoxFlag(LE_TRUE); } else { if(this->level1ShowCollisionBoxesFlag() && param->engine->keyEvent(SDL_KEYUP, SDLK_9)) { this->level1SetCollBoxesVisible(param->engine, LE_FALSE); this->level1SetShowCollisionBoxFlag(LE_FALSE); } } #endif }
Game::level1HandleShip();
#include "../parameter.h" #include "../include_level1/res_intro_flight.h" void Game::level1HandleShip(void * pointer) { Parameter param = (Parameter) pointer; static double sizeFactor = param->engine->modelGetSizeFactor(MODEL_VERA); static bool up = LE_FALSE; // make ship move up and down in Z if(!this->vera.collide) { if(up) { sizeFactor += param->engine->getTimestep() * 0.05f; if(sizeFactor >= 1.0f) { sizeFactor = 1.0f; up = LE_FALSE; } } else { sizeFactor -= param->engine->getTimestep() * 0.05f; if(sizeFactor <= 0.9f) { sizeFactor = 0.9f; up = LE_TRUE; } } } else {sizeFactor = 1.0f;} param->engine->modelSetSizeFactor(MODEL_VERA, sizeFactor); // control ship if(this->gameState == asteroidFlight) {this->level1ControlShip(param->engine, LE_TRUE);} if(this->gameState == satelliteReached) {this->level1ControlShip(param->engine, LE_FALSE);} }
Game::level1ControlShip:
#include "../config.h" #include "../parameter.h" #include "../res.h" #include "../include_level1/res_intro_flight.h" #include "../include_level1/res_asteroid_flight.h" void Game::level1ControlShip(void * pointer, bool controlAble) { Parameter param = (Parameter) pointer; static glm::vec2 currentSpeedLeft = {0.0f, 0.0f}; glm::vec2 speedLeft = param->engine->modelGetDirection(MODEL_VERA, LEFT); static const double INCREASE_SPEED_LEFT = (1.0f / VERA_INCREASE_LEFT) * speedLeft.x * (-1.0f); static glm::vec2 currentSpeedRight = {0.0f, 0.0f}; glm::vec2 speedRight = param->engine->modelGetDirection(MODEL_VERA, RIGHT); static const double INCREASE_SPEED_RIGHT = (1.0f / VERA_INCREASE_RIGHT) * speedRight.x; glm::vec2 positionVera = param->engine->modelGetPositionD(MODEL_VERA); static SDL_Point sizeVera = param->engine->modelGetSize(MODEL_VERA); const double ANIMATION_FACTOR = 0.15f; bool veraDoesntMove = currentSpeedLeft.x == 0.0f && currentSpeedRight.x == 0.0f; #ifdef BUILD_MOBILE SDL_Point mousePos = param->engine->mouseGetPosition(); bool fingerIsLeftToVera = LE_FALSE; bool fingerIsRightToVera = LE_FALSE; bool fingerUp = LE_FALSE; bool fingerDown = LE_FALSE; #endif if(controlAble) { #ifdef BUILD_MOBILE fingerUp = param->engine->mouseEvent(SDL_MOUSEBUTTONUP, SDL_BUTTON_LEFT); fingerDown = param->engine->mouseEvent(SDL_MOUSEBUTTONDOWN, SDL_BUTTON_LEFT); fingerIsLeftToVera = mousePos.x < (positionVera.x + (sizeVera.x * 0.5f)); fingerIsRightToVera = mousePos.x > (positionVera.x + (sizeVera.x * 0.5f)); if(fingerIsLeftToVera) { // brake left? if(fingerUp) { if(currentSpeedRight.x == 0.0f) {this->vera.controls = VERA_BRAKES_LEFT;} else {this->vera.controls = VERA_BRAKES_RIGHT;} } else { if(fingerDown) { // brake right? if(currentSpeedRight.x != 0.0f) {this->vera.controls = VERA_BRAKES_RIGHT;} else { // increase left? if(currentSpeedLeft.x > speedLeft.x) {this->vera.controls = VERA_SPEEDUP_LEFT;} else { // fly left? this->vera.controls = VERA_FLIES_LEFT; } } } } } if(fingerIsRightToVera) { // brake right? if(fingerUp) { if(currentSpeedLeft.x == 0.0f) {this->vera.controls = VERA_BRAKES_RIGHT;} else {this->vera.controls = VERA_BRAKES_LEFT;} } else { if(fingerDown) { // brake left? if(currentSpeedLeft.x != 0.0f) {this->vera.controls = VERA_BRAKES_LEFT;} else { // increase right? if(currentSpeedRight.x < speedRight.x) {this->vera.controls = VERA_SPEEDUP_RIGHT;} else { // fly right? this->vera.controls = VERA_FLIES_RIGHT; } } } } } #endif } else { if(veraDoesntMove) {this->vera.controls = VERA_DOESNT_MOVE;} else { if(this->vera.controls == VERA_SPEEDUP_LEFT || this->vera.controls == VERA_FLIES_LEFT) {this->vera.controls = VERA_BRAKES_LEFT;} if(this->vera.controls == VERA_SPEEDUP_RIGHT || this->vera.controls == VERA_FLIES_RIGHT) {this->vera.controls = VERA_BRAKES_RIGHT;} } } // control vera switch(this->vera.controls) { case VERA_SPEEDUP_LEFT: { currentSpeedLeft.x -= (float)(INCREASE_SPEED_LEFT * param->engine->getTimestep()); if(currentSpeedLeft.x < speedLeft.x) {currentSpeedLeft.x = speedLeft.x;} positionVera.x += (float)(currentSpeedLeft.x * param->engine->getTimestep()); param->engine->modelSetPosition(MODEL_VERA, positionVera.x, positionVera.y); this->vera.currentFrame += currentSpeedLeft.x * ANIMATION_FACTOR * param->engine->getTimestep(); if(this->vera.currentFrame < 1.0f) {this->vera.currentFrame = 1.0f;} param->engine->modelFocusTextureSourceRect(MODEL_VERA, TEXTURE_DEFAULT, (uint32_t) this->vera.currentFrame); } break; case VERA_FLIES_LEFT: { param->engine->modelMoveDirection(MODEL_VERA, LEFT); } break; case VERA_BRAKES_LEFT: { currentSpeedLeft.x += (float)(INCREASE_SPEED_LEFT * param->engine->getTimestep()); if(currentSpeedLeft.x > 0.0f) {currentSpeedLeft.x = 0.0f;} positionVera.x += (float)(currentSpeedLeft.x * param->engine->getTimestep()); param->engine->modelSetPosition(MODEL_VERA, positionVera.x, positionVera.y); this->vera.currentFrame -= currentSpeedLeft.x * ANIMATION_FACTOR * param->engine->getTimestep(); if(this->vera.currentFrame > 12.0f || currentSpeedLeft.x == 0.0f) {this->vera.currentFrame = 12.0f;} param->engine->modelFocusTextureSourceRect(MODEL_VERA, TEXTURE_DEFAULT, (uint32_t) this->vera.currentFrame); } break; case VERA_SPEEDUP_RIGHT: { currentSpeedRight.x += (float)(INCREASE_SPEED_RIGHT * param->engine->getTimestep()); if(currentSpeedRight.x > speedRight.x) {currentSpeedRight.x = speedRight.x;} positionVera.x += (float)(currentSpeedRight.x * param->engine->getTimestep()); param->engine->modelSetPosition(MODEL_VERA, positionVera.x, positionVera.y); this->vera.currentFrame += currentSpeedRight.x * ANIMATION_FACTOR * param->engine->getTimestep(); if(this->vera.currentFrame > 21.0f) {this->vera.currentFrame = 21.0f;} param->engine->modelFocusTextureSourceRect(MODEL_VERA, TEXTURE_DEFAULT, (uint32_t) this->vera.currentFrame); } break; case VERA_FLIES_RIGHT: { param->engine->modelMoveDirection(MODEL_VERA, RIGHT); } break; case VERA_BRAKES_RIGHT: { currentSpeedRight.x -= (float)(INCREASE_SPEED_RIGHT * param->engine->getTimestep()); if(currentSpeedRight.x < 0.0f) {currentSpeedRight.x = 0.0f;} positionVera.x += (float)(currentSpeedRight.x * param->engine->getTimestep()); param->engine->modelSetPosition(MODEL_VERA, positionVera.x, positionVera.y); this->vera.currentFrame -= currentSpeedRight.x * ANIMATION_FACTOR * param->engine->getTimestep(); if(this->vera.currentFrame < 12.0f || currentSpeedRight.x == 0.0f) {this->vera.currentFrame = 12.0f;} param->engine->modelFocusTextureSourceRect(MODEL_VERA, TEXTURE_DEFAULT, (uint32_t) this->vera.currentFrame); } break; default: {} break; } // avoid leaving screen to left or right if(positionVera.x < 0) { positionVera.x = 0; currentSpeedLeft.x = 0.0f; } if(positionVera.x > param->engine->getScreenWidth() - sizeVera.x) { param->engine->modelSetPosition(MODEL_VERA, param->engine->getScreenWidth() - sizeVera.x, positionVera.y); currentSpeedRight.x = 0.0f; } }
Die Game::level1ControlShip Methode ist noch ziemlich mächtig (viel). Das soll noch modularer gestaltet werden. Aber ich teste das ja auch alles noch.
Aber das ist genau, was komplett passiert:
- Der vorherige Spielzustand wird verlassen und "Asteroid Level" wird betreten.
- Game::stageAsteroidFlight() wird aufgerufen.
- Game::level1HandleShip() wird aufgerufen.
- Game::level1ControlShip() wird aufgerufen.
- Dort gibt es die exception nachdem modelGetDirection() aufgerufen wird und über den Lock Guard nicht hinaus geht!
-
Funktioniert der Code denn Singlethreaded?
Ja. Wenn ich alle lock guards entfernen würde, würde es funktionieren. Ich habe die ganze Anwendung aber um einen Memory Organizer erweitert. Der soll Inhalte selbst laden / entladen, wenn ein bestimmter Spielabschnitt erreicht wurde. Dieser Memory Organizer wird in einem neuen Thread gestartet, wenn er merkt, dass er neue Sachen laden soll. Das funktioniert auch. Aber das ist der Grund warum die ganze Engine thread safe sein muss. Weil nebenbei soll das Spiel ja auch weiter laufen können.
Später - das passiert jetzt aber noch nicht - soll der sogar in der Lage sein wirklich parallel Inhalte zu laden und zu entladen, ohne Ladezeiten. Das hängt auch mit der Contentplanung des Spiels zusammen.
Generell sehe ich kein Fehler in deiner Verwendung von der Mutex, nach wie vor glaube ich, dass mirgendwas nicht initialisiert ist.
Die Frage wäre dann was und warum. Ich initialisiere ja die engine am Anfang und dort scheint auch alles zu funktionieren. Der Mutex ist ja wie gesagt ein Attribut in der Engine Klasse. Er müsste eigentlich die ganze Zeit verfügbar sein?
P.S: Gibt es einen Grund dafür, dass du konstant C-Strings verwendest, anstelle von std::string?
Nicht wirklich. Ich kenne die Klasse, habe sie aber einfach nur noch nie genutzt.
-
Crosspost übrigens da drüben.
-
@lynarstudios sagte in std::lock_guard (mutex) produziert deadlock:
Ja. Wenn ich alle lock guards entfernen würde, würde es funktionieren. Ich habe die ganze Anwendung aber um einen Memory Organizer erweitert. Der soll Inhalte selbst laden / entladen, wenn ein bestimmter Spielabschnitt erreicht wurde. Dieser Memory Organizer wird in einem neuen Thread gestartet, wenn er merkt, dass er neue Sachen laden soll. Das funktioniert auch. Aber das ist der Grund warum die ganze Engine thread safe sein muss. Weil nebenbei soll das Spiel ja auch weiter laufen können.
Ziemlich viel im Konjunktiv. Aber ok. Kann es sein, dass ein Thread deine Engine schon wieder zerstört während andere noch nicht fertig mit ihrer Arbeit sind?
Die Frage wäre dann was und warum. Ich initialisiere ja die engine am Anfang und dort scheint auch alles zu funktionieren. Der Mutex ist ja wie gesagt ein Attribut in der Engine Klasse. Er müsste eigentlich die ganze Zeit verfügbar sein?
Das würde passen, wenn man nicht sicher stellt, dass alle Arbeit fertig ist, bevor man anfängt aufzuräumen.
P.S: Gibt es einen Grund dafür, dass du konstant C-Strings verwendest, anstelle von std::string?
Nicht wirklich. Ich kenne die Klasse, habe sie aber einfach nur noch nie genutzt.
Das ist einer der Punkte, den ich weiter oben mit "modernen C++" Strukturen meine. Du verwendest recht viel "new" und "delete". Entsprechende Stichwort wäre "Resource acquisition is initialization" (RAII). Vor allem verwendung von Smart Pointern und anderen Klassen die das entsprechend kapseln. (Z.B. der lock_quard ist ein Beispiel für das Idiom.)