std::lock_guard (mutex) produziert deadlock
-
@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.)