Thread: Semaphore und Mutexe

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
Chimorin
Beiträge: 451
Registriert: 30.01.2013 16:11
Computerausstattung: MSI GTX 660 OC mit TwinFrozr III
6Gb DDR 3 RAM
AMD Phenom II X4 B55 @ 3,6GHz
Windows 7 Home Premium 64-bit

Thread: Semaphore und Mutexe

Beitrag von Chimorin »

Heyho,

könnten erfahrene Threadspezialisten bitte mal kurz über diesen Code schauen? Ich habe ihn aus der Hilfe zusammengeschustert ^^
Soll für ein Spiel sein, d.h. die Keyboardreleased werden dann zu Spielerbewegungen,... verarbeitet.

Bitte alles, was irgendwie nicht sauber programmiert ist sofort melden; Ich würde gerne wissen, ob das so gut ist oder ob es Probleme geben könnte (Speziell beim Durchlaufzähler im Thread).

Falls ihr den Code auch ausprobieren wollt, speichert ihr ihn einfach im 3D Examples Ordner.

Code: Alles auswählen

EnableExplicit


Structure FPS
  zeit.i
  zaehler.i
EndStructure


Define.FPS FPS
Define.i frameZeit, Quit
Global Semaphore = CreateSemaphore()
Global Mutex = CreateMutex()


Global NewList Queue.i()


;Fenster
Enumeration
  #Window3D_0
EndEnumeration


;Gadgets
Enumeration
  #Text3D_0
EndEnumeration


;Kameras
Enumeration
  #kamera_0
EndEnumeration


;Events
Enumeration
  #Escape_Up
  #A_Up
  #S_Up
EndEnumeration


Procedure Locker(event.i)
  If TryLockMutex(Mutex)        ;Wenn nicht erfolgreich, verarbeitet Hauptschleife gerade Eingaben; Ein bisschen Schwund ist Standart :D
    LastElement(Queue())
    AddElement(Queue())
    Queue() = event
    UnlockMutex(Mutex)    
    
    ; Signalisiere, dass es ein neues Queue-Element gibt
    SignalSemaphore(Semaphore)
  EndIf
EndProcedure


Procedure InputHandler(nr.i)
  Protected.FPS FPS
  
  Repeat
    FPS\zaehler = FPS\zaehler + 1       ;Zählt die vergangenen Frames
    
    If FPS\zaehler > 200        ;FPS-Grenze bei 200 FPS, die Eingaben dürfen ruhig schneller sein.
      Delay(FPS\zaehler - 200)
      ;Else
      ;Hier kann man dann einen Verzug an den Server senden.
    EndIf
    
    If ElapsedMilliseconds() - FPS\zeit >= 1000       ;Damit werden die Frames pro Sekunde (1000ms) ermittelt
      FPS\zeit = ElapsedMilliseconds()
      FPS\zaehler = 0
    EndIf
    
    ExamineKeyboard()
    ExamineMouse()
    
    InputEvent3D(MouseX(), MouseY(), MouseButton(#PB_MouseButton_Left))
    
    If KeyboardReleased(#PB_Key_Escape)
      Locker(#Escape_Up)
    EndIf
    
    If KeyboardReleased(#PB_Key_A)
      Locker(#A_Up)
    EndIf
    
    If KeyboardReleased(#PB_Key_S)
      Locker(#S_Up)
    EndIf
    
  ForEver
EndProcedure


Procedure Window3D()
  OpenWindow3D(#Window3D_0, 10, 10, 400, 400, "Threadtest")
  TextGadget3D(#Text3D_0, 10, 10, 380, 380, "")
EndProcedure


InitEngine3D()
InitSprite()
InitKeyboard()
InitMouse()


Add3DArchive("Data/GUI", #PB_3DArchive_FileSystem)

OpenScreen(1920, 1080, 32, "Threadtest")
CreateCamera(#kamera_0, 0, 0, 100, 100)
Window3D()

CreateThread(@InputHandler(), -1)

Repeat
  
  FPS\zaehler = FPS\zaehler + 1       ;Zählt die vergangenen Frames
  
  If FPS\zaehler > 100        ;FPS-Grenze bei 100 FPS
    Delay(FPS\zaehler - 100)
    ;Else
    ;Hier kann man dann einen Verzug an den Server senden.
  EndIf
    
  If ElapsedMilliseconds() - FPS\zeit >= 1000       ;Damit werden die Frames pro Sekunde (1000ms) ermittelt
    FPS\zeit = ElapsedMilliseconds()        ;Zeit, zu der diese
    FPS\zaehler = 0
  EndIf
  
  
  While TrySemaphore(Semaphore)
    If TryLockMutex(Mutex)
      FirstElement(Queue())
      Select Queue()
        Case #Escape_Up
          Quit = #True
          
        Case #S_Up
          SetGadgetText3D(#Text3D_0, GetGadgetText3D(#Text3D_0) + "s")
          
        Case #A_Up
          SetGadgetText3D(#Text3D_0, GetGadgetText3D(#Text3D_0) + "a")
          
      EndSelect
      DeleteElement(Queue())
      UnlockMutex(Mutex)
    Else
      SignalSemaphore(Semaphore)
    EndIf
  Wend

  
  
  frameZeit = RenderWorld(frameZeit)        ;Die Physik wird mit der Zeit des vorletzten Frames gefüttert, als Ergebnis die Zeit des letzten Frames.
  
  FlipBuffers()
Until Quit
End
Gruß

Banane
Zuletzt geändert von Chimorin am 09.09.2013 08:22, insgesamt 2-mal geändert.
Bild

- formerly known as Bananenfreak -
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Thread: Semaphore und Mutexe

Beitrag von NicTheQuick »

Ich verstehe das mit dem Delay() nicht. Also falls die Schleife nach einer Sekunde öfter als 200 Mal durchlaufen wurde, wird ein Delay gemacht mit der Anzahl der Schleifendurchläufe minus 200 in Millisekunden? Wie kommst du darauf die Schleifendurchläufte plötzlich auf Millisekunden zu mappen? Und dann wird das Delay auch immer nur im Sekundentakt gemacht.
Es könnte folgendes passieren: 1000 ms sind rum und der Zähler hat bis 2000 gezählt. Jetzt wird das If ausgeführt und da der Zähler schon bei 2000 ist, werden 1800 ms gewartet.

Ach nee, moment. Vor dem "If FPS\zaehler > 200" steht ja noch "FPS\zaehler = 0". Okay, dann passiert das Problem zwar nicht, aber dann ist auch das "If" auch echt unnötig. <)

Ansonsten fällt mir noch ein Problem auf. Dein "While TrySemaphore(Semaphore)" ist zunächst ja korrekt so, aber wenn direkt danach zufällig der Mutex nicht gelockt werden kann, dann verlierst du ja ein Event. Es ist dann zwar noch in der LinkedList drin, aber du kriegst kein SignalSemaphore mehr dafür. Statt dem 'TryLockMutex' solltest du also 'LockMutex' nutzen.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Thread: Semaphore und Mutexe

Beitrag von STARGÅTE »

Erst mal, warum verwendest du hier einen Thread?
Hat in meinen Augen wenig sinn. Es wäre einfach wenn du die Funktion InputHandler() direkt aufrufst, natürlich in abgeänderter form.

Zum Code:

Code: Alles auswählen

While TrySemaphore(Semaphore)
    If TryLockMutex(Mutex)
Das geht so nicht, dadurch könnte dein Semaphore runter zählen, aber wenn der Mutex nicht frei ist, wird das Event garnicht verarbeitet und dann nie mehr.

Edit: Immer wieder erstaunlich wie Inhaltlich ähnlich unsere Antworten sind NicTheQuick
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
Chimorin
Beiträge: 451
Registriert: 30.01.2013 16:11
Computerausstattung: MSI GTX 660 OC mit TwinFrozr III
6Gb DDR 3 RAM
AMD Phenom II X4 B55 @ 3,6GHz
Windows 7 Home Premium 64-bit

Re: Thread: Semaphore und Mutexe

Beitrag von Chimorin »

Vielen Dank für die Antworten.

Den Code für den FPS-Zähler habe ich von einem User namens adra (Development-lounge). So müsste das doch funktionieren (So war das von ihm glaube ich auch gedacht...).

Das TryLockMutex() wurde durch ein Else mit SignalSemaphore() erweitert, Problem behoben.

Im Thread wird jetzt nicht gewartet, bis Zugriff vorhanden ist; Richtig oder Falsch?

@STARGÅTE:
Wie macht ein Thread dann Sinn?
Wenn das Spiel anfängt wegen RenderWorld() zu ruckeln (Schlechter PC, zu viele Batches, zu viele Tris,...), dann wird wenigstens die Tastatur und Mauseingabe noch verarbeitet (Ich glaube jetzt allerdings, dass das nicht wirklich ein Totschlagargument ist).
Ich würde 2 Threads für einen Mehrspielermodus benutzen, für das Empfangen und Senden der Daten (Oder nach TCP und UDP getrennt?!).
Wofür kann ich Threads benutzen? In meinem Mapeditor gibt es einen kurzen Abfall der Framerate, wenn Terrain Stücke nachgeladen werden. Kann ich diese etwa in einem oder mehreren Threads laden lassen? Oder gibt es da wieder Probleme mit halb fertigen Terrains beim nächsten RenderWorld()?
Bild

- formerly known as Bananenfreak -
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Thread: Semaphore und Mutexe

Beitrag von NicTheQuick »

Wenn du schon die Game- von der Render-Engine trennen willst, dann auch bitte ganz. Soll heißen in dem Thread, wo die Eingaben aufgenommen werden, sollten auch die Spieler bewegt und die Physik berechnet werden.
Sonst macht es wenig Sinn. Gerade der Befehl 'KeyboardReleased()' gibt ja nur #True zurück, wenn die Taste seit dem letzten Mal gedrückt und wieder losgelassen wurde. Ob du das dann im Thread prüfst oder den Befehl einfach in der Hauptschleife aufrufst, macht dabei keinen Unterschied. Bei 'KeyboardPushed()' macht das schon mehr Sinn, aber auch nur, wenn die Bewegungen und die Physik dann auch in dem selben Thread abgearbeitet werden.
Würden sie erst abgearbeitet werden, wenn sie in der Hauptschleife ankommen, dann könntest du das 'KeyboardPushed()' auch genau so gut da hin machen.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Thread: Semaphore und Mutexe

Beitrag von STARGÅTE »

Ein Thread mach dann sinn, wenn man eine Procedure hat, die sehr viel Zeit in Anspruch nimmt (einige Sekunden) und diese aber gerne im "Hintergrund" oder Parallel zum Haupprogramm laufen lassen möchte.

Ein klassisches Beispiel (wie ich finde) ist OpenNetworkConnection().
Diese Funktion benötigt schon mal einige Sekunden, gerade dann wenn keine Verbindung hergestellt werden kann.
Damit das Hauptprogramm (zB ein Screen mit einem Ladekreis) aber nicht pausiert wird, sondern weiter die Animation des Ladekreises anzeigt, wäre es hier geeignet OpenNetworkConnection() in einen Thread auszulagern.

In deinem Beispiel hast du keine solche Zeitintensive Funktion.
Und wie NicTheQuick auch schon sagte, ist es auch quatsch zwar KeyboardReleased() zu registieren, aber trotzdem bei LAG nur in jedem Frame zu verarbeiten.

Auch wirst du nicht durch schnelles drücken und loslassen eine höhere Frequenz erreichen als deine Framerate.
Und wenn es LAGt will ich sowieso nicht weiter spielen ^^

Viel wichtier ist, dass Objekte die sich bewegen, immer die gleiche Geschwindigkeit haben, unabhängig von der Framerate.
Ein einfaches x+v geht also nicht, sondern hier muss x+v*dt gerechnet werden, wobei dt die (durchschnitts) Zeit zwischen zwei Frames ist. Dann benötigst du auch keine Frame-Bremse, wie du sie aktuell eingebaut hast.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
Chimorin
Beiträge: 451
Registriert: 30.01.2013 16:11
Computerausstattung: MSI GTX 660 OC mit TwinFrozr III
6Gb DDR 3 RAM
AMD Phenom II X4 B55 @ 3,6GHz
Windows 7 Home Premium 64-bit

Re: Thread: Semaphore und Mutexe

Beitrag von Chimorin »

Die Framebremse habe ich nur drin, damit die CPU-Nutzung nicht auf 100% springt...

Hmm, ok. Zeitintensive Sachen. Da zählt wohl das Nachladen von Terrainstücken dazu. Ich probiere das mal aus; Mal sehen, ob es Komplikationen gibt :D
Bild

- formerly known as Bananenfreak -
Benutzeravatar
PMV
Beiträge: 2765
Registriert: 29.08.2004 13:59
Wohnort: Baden-Württemberg

Re: Thread: Semaphore und Mutexe

Beitrag von PMV »

Noch 2 ... 3 Dinge die du beachten solltest:
1. Alles, was mit PB-OGRE zu tun hat, ist nicht Threadsicher.
Da zählt auch das InputEvent3D() dazu.

2. Da die PB-Linked-Listen nie mals in 2 Threads gleichzeitig
verwendet werden können, sind diese komplett ungeeignet
als Queue zum Austausch von Events zwischen Threads.
Nic hat ja letztens erst ne schön saubere Variante als Modul
gepostet. PB bringt beim Thema Threads leider recht wenig mit.

3. Nimm besonders viel Abstand von unsauberen Implementierungen
Wenn du schon bei der Entwicklung weist, das etwas nicht 100%
funktioniert, dann such eine Alternative! Die beste Spielidee bringt
gar nichts, wenn durch unsaubere Arbeit der Spielspaß bereits im
Keim erstickt wird. Die Steuerung in einen Thread aus zu lagern
macht man deswegen, damit das Spiel sauber darauf reagieren kann.
Dann Eingaben aber bewusst zu verschlucken ... lol! :?

MFG PMV
alte Projekte:
TSE, CWL, Chatsystem, GameMaker, AI-Game DLL, Fileparser, usw. -.-
Benutzeravatar
Chimorin
Beiträge: 451
Registriert: 30.01.2013 16:11
Computerausstattung: MSI GTX 660 OC mit TwinFrozr III
6Gb DDR 3 RAM
AMD Phenom II X4 B55 @ 3,6GHz
Windows 7 Home Premium 64-bit

Re: Thread: Semaphore und Mutexe

Beitrag von Chimorin »

PMV, wie meinst du "sauber darauf reagieren"?

Ich werde wohl die Maus- und Tastatursachen in der Hauptschleife lassen und nichts davon in einen Thread stecken.

EDIT: Ich muss mich korrigieren. Die CPU-Nutzung schnellt nicht nach oben <)
Bild

- formerly known as Bananenfreak -
Benutzeravatar
PMV
Beiträge: 2765
Registriert: 29.08.2004 13:59
Wohnort: Baden-Württemberg

Re: Thread: Semaphore und Mutexe

Beitrag von PMV »

http://forums.purebasic.com/german/view ... =4&t=25908

PS: genau so gibt es auch Tastaturen, die besonders hohe Abtastraten haben.
Der Aufwand dafür ist etwas höher und ob sich das wirklich auszahlt ... wohl
eher nur wenn man einen Titel hat, der im E-Sport benutzt wird. :?
Aber sauber ist sauber und mit der Event-Queue hast du auch den richtigen
Ansatz. :wink:

MFG PMV
alte Projekte:
TSE, CWL, Chatsystem, GameMaker, AI-Game DLL, Fileparser, usw. -.-
Antworten