Zugriffe auf Datenstruktur synchronisieren

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut

Zugriffe auf Datenstruktur synchronisieren

Beitragvon mintpc » 30. Aug 2017, 10:17 Zugriffe auf Datenstruktur synchronisieren

Hallo zusammen,

wahrscheinlich schon zig mal gefragt, gefunden hab ich aber nichts. Wenn schon gelöst, reicht mir ein Link.

Mein Problem: Ich erzeuge viele Kreise, die als Liste verwaltet werden:

Code: Alles auswählen
  TKreis = class (TShape)
      public RtgX, RtgY, Groesse : integer;
      public next: TKreis;
      procedure bewegen;
      constructor create (AOwner: TComponent); override;
      procedure setGroessePosition(inX, inY, inGroesse: integer);
      procedure MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  end;
 
  TListe = class
      erster : TKreis;
      procedure hinzufuegen (inK: TKreis);
      procedure loesche (inK: TKreis);
  end


Asteroidsjpg.jpg


Wie beim alten "Asteroids" teilen sich die Kreise beim Draufklicken, werden kleiner, die Klone werden in die Liste eingefügt und nach 3 Klicks wird der entsprechende Kreis gelöscht (aus der Datenstruktur entfernt).
Über einen Timer wird die Liste mit einer Schleife durchlaufen, in der die Kreis bewegt werden.

Nun kommt es bei schnellen Klicks und ca. 50-100 Kreisen wohl dazu, dass "gleichzeitig / nebenläufig" ein Kreis gelöscht und die Liste zum Bewegen der Kreise im Timer durchlaufen wird.

Dabei greift die Schleife im Timer wohl auf schon gelöschte Objekte zu und es gibt den SIGSEGV.

Frage: Gibt es die Möglichkeit, die "Nebenläufigkeit" der Prozesse einzuschränken, dass nur gelöscht wird, wenn die Bewegen-Schleife fertig ist?

Vielen Dank schonmal
mintpc
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
mintpc
 
Beiträge: 112
Registriert: 6. Sep 2010, 17:39
Wohnort: Mailand
OS, Lazarus, FPC: Win 7 (L 1.6 FPC 3.0.0) | 
CPU-Target: Win 7
Nach oben

Beitragvon wp_xyz » 30. Aug 2017, 11:07 Re: Zugriffe auf Datenstruktur synchronisieren

Du musst eine Statusvariable einführen (FLockList: Integer), die du immer dann, wenn die Liste bearbeitet wird, um 1 erhöhst und, wenn die Bearbeitung beendet ist, wieder um 1 erniedrigst. In allen Routinen, die die Liste bearbeiten möchten, führst du den Aufruf nur dann (oder besser: erst) aus, wenn FLockList = 0 ist.

Code: Alles auswählen
type
  TKreis = class (TShape)
      ...
      procedure MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  end;
 
  TListe = class
  private
    FLockList: Integer;
  public
    function IsLocked: Boolean;
    procedure Hinzufuegen(inK: TKreis);
    procedure Loesche(ink: TKreis);
  end;
 
procedure TKreis.MouseDown(...);
begin
  inherited;
  repeat
  until not Liste.IsLocked// --> der Kreis muss die Liste kennen!
  Liste.Hinzufuegen(...);
end;
 
function TList.IsLocked: Boolean;
begin
  Result := FLockList <> 0;
end;
 
procedure TList.Loesche(inK: TKreis); // genauso: Hinzufuegen(...)
begin
  inc(FLockList);
  try    // Mit try-finally sicherstellen, dass die routine bei einem Fehler in einem Zustand verlassen wird, in dem die Liste bearbeitet werden kann
    ...
  finally
    dec(FLockList);
  end;
end;
wp_xyz
 
Beiträge: 2184
Registriert: 8. Apr 2011, 08:01

Beitragvon braunbär » 30. Aug 2017, 13:00 Re: Zugriffe auf Datenstruktur synchronisieren

Würde ich anders realisieren. Mit einem zusätzlichen Boolean-Feld clicked bei jedem Kreis.

Die Onclick Routine braucht dann nur clicked:=true setzen und kehrt sofort zurück, statt auf die Beendigung einer kompletten "Bewegungsschleife" zu warten. Die Reaktion auf den Click wird dann in der Routine erledigt, die auch für das Bewegen der Kreise zuständig ist, also irgendwas auf die Art
Code: Alles auswählen
 
if self.clicked
then self.KreisSpalten
else self.KreisBewegen
 


Und wenn der Kreis klein genug ist, wird er in der Routine KreisSpalten gelöscht.

BTW: Warum verwendest du nicht die vorgegebene Listenstruktur TLIST statt eines "next" Zeigers im Objekt? Genau für solche Anwendungen ist doch diese Klasse gemacht worden.

@wpxyz
In Delphi würde
Code: Alles auswählen
  repeat
  until not Liste.IsLocked// --> der Kreis muss die Liste kennen!
 

zu einer Endlosschleife führen und das Programm "einfrieren", da müsste es heißen
Code: Alles auswählen
  repeat Application.ProcessMessages
  until not Liste.IsLocked// --> der Kreis muss die Liste kennen!
 

Braucht man in Free Pascal nichts, was dem Delphi Application.ProcessMessages entsprechen würde?
braunbär
 
Beiträge: 162
Registriert: 8. Jun 2017, 17:21

Beitragvon wp_xyz » 30. Aug 2017, 13:54 Re: Zugriffe auf Datenstruktur synchronisieren

So genau habe ich mir das nun auch nicht überlegt, der Fragesteller hat zu wenig Information angegeben, um ein konkretes funktionsfähiges Beispiel auszuarbeiten.
wp_xyz
 
Beiträge: 2184
Registriert: 8. Apr 2011, 08:01

Beitragvon Warf » 30. Aug 2017, 17:05 Re: Zugriffe auf Datenstruktur synchronisieren

Verwende kein Boolean Feld. Zugriffe auf booleanfelder sind nicht atomar. Genau dafür gibt es critical sections, diese verwenden vom Betriebssystem bereitgestellte atomare get und set Funktionen.

Da ich grade am Handy bin kann ich dir nicht ausführlich erklären warum das notwendig ist, aber wenn die Aktionen nicht atomar sind, kann es sein das trotzdem beide nebenläufigen Prozesse die critical section ausführen.

Es gibt übrigens eine listenklasse die das bereits implementiert, ich glaube tthreadlist oder so
Warf
 
Beiträge: 589
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Mathias » 30. Aug 2017, 17:16 Re: Zugriffe auf Datenstruktur synchronisieren

Dazu habe ich einen kleine Versuch gemacht.

Code: Alles auswählen
procedure TForm1.FormCreate(Sender: TObject);
var
  i, l: integer;
begin
  l := 100;
  SetLength(Shape, l);
  for i := 0 to l - 1 do begin
    Shape[i] := TShape.Create(Self);
    Shape[i].Parent := Self;
    Shape[i].Left := round(Random(400));
    Shape[i].Top := round(Random(400));
    Shape[i].Shape := stCircle;
    Shape[i].Tag := i;
    Shape[i].OnClick := @ShapeClick;
  end;
end;
 
procedure TForm1.ShapeClick(Sender: TObject);
var
  sh: TShape;
begin
  sh := Shape[TShape(Sender).Tag];
  sh.Visible := False;
end;

Dabei muss beachtet werden, das der Kreis auch verschwindet, wen man nebenan klickt.
Der Grund, die Kreis-Shape, wird beim reinklicken als Quadrat angenommen.

Dies nur so als Tip. :wink:
Mit Lazarus sehe ich gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3046
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon mintpc » 30. Aug 2017, 20:13 Re: Zugriffe auf Datenstruktur synchronisieren

Erstmal vielen Dank für die vielen Antworten.

Die Variante von wp_xyz hatte ich so schon ausprobiert, allerdings mit ner booleschen Variablen. Wenn warf Recht hat, dann müsste das ja dann mit integer-Werten klappen.

Braunbärs Lösung klingt auch verführerisch, werde ich auch mal ausprobieren.

Bei Mathias scheint mir, dass der Kreis nur unsichtbar gemacht werden soll, und nicht aus der Liste gelöscht wird, richtig?
Das hatte ich auch schon ausprobiert und das funktioniert auch, wäre aber für mich nur eher so ne Notlösung, falls gar nichts
anderes geht.


Nachher probier ich mal alles aus und berichte dann.

P.S: Das ganze "Programmfragment" hab ich mal angehängt, weil wp_xyz danach gefragt hatte. Der Code ist aber
"schrumpelig", weil ich da grad viel rumprobiert hab. Keine Konstruktoren und so.
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Zuletzt geändert von mintpc am 30. Aug 2017, 20:49, insgesamt 2-mal geändert.
mintpc
 
Beiträge: 112
Registriert: 6. Sep 2010, 17:39
Wohnort: Mailand
OS, Lazarus, FPC: Win 7 (L 1.6 FPC 3.0.0) | 
CPU-Target: Win 7
Nach oben

Beitragvon mintpc » 30. Aug 2017, 20:47 Re: Zugriffe auf Datenstruktur synchronisieren

So, getestet.

Die Version von wp_xyz funktioniert leider nicht. Vielleicht ist da von mir ein Umsetzungsfehler drin, aber es kommen recht schnell wieder die Zugriffsfehler. Ich hab die Version angehängt. (Wenn ich was falsch implementiert habe, schonmal vorab Entschuldigung)

Die Version von Braunbär funktioniert. Wildes Mausgeklicke führt zu keinem Fehler mehr. Die Version ist auch angehängt.

Vielen vielen Dank für die großartigen Hilfen. :D
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
mintpc
 
Beiträge: 112
Registriert: 6. Sep 2010, 17:39
Wohnort: Mailand
OS, Lazarus, FPC: Win 7 (L 1.6 FPC 3.0.0) | 
CPU-Target: Win 7
Nach oben

Beitragvon Warf » 30. Aug 2017, 21:40 Re: Zugriffe auf Datenstruktur synchronisieren

mintpc hat geschrieben:So, getestet.

Die Version von wp_xyz funktioniert leider nicht. Vielleicht ist da von mir ein Umsetzungsfehler drin, aber es kommen recht schnell wieder die Zugriffsfehler. Ich hab die Version angehängt. (Wenn ich was falsch implementiert habe, schonmal vorab Entschuldigung)

Die Version von Braunbär funktioniert. Wildes Mausgeklicke führt zu keinem Fehler mehr. Die Version ist auch angehängt.

Vielen vielen Dank für die großartigen Hilfen. :D


Dennoch, siehe meinen vorherigen Post, solltest du eine critical section statt einem boolean Feld verwenden. Die Chance das es dir um die Ohren fliegt ist zwar gering, mit critical sections allerdings gar nicht vorhanden.

Wenn dann mach es direkt richtig, und fang nicht mit so einem halbgaren Müll wie booleanfeldern an, bevor du dir noch so einen Fehler angewöhnst
Warf
 
Beiträge: 589
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon braunbär » 30. Aug 2017, 21:49 Re: Zugriffe auf Datenstruktur synchronisieren

Warf hat geschrieben:Verwende kein Boolean Feld. Zugriffe auf booleanfelder sind nicht atomar.

Wie kommst du darauf?
boolean.PNG

Viel atomarer als ein movb oder ein cmpb (s.Anhang) geht nicht. Ob auf der untersten Hardwareebene ein paar zusätzliche Taktzyklen gebraucht werden, um ein einzelnes Byte zu schreiben (beim lesen nicht einmal das), ist für den Programmablauf egal. Da fährt kein Interrupt dazwischen.
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
braunbär
 
Beiträge: 162
Registriert: 8. Jun 2017, 17:21

Beitragvon Warf » 30. Aug 2017, 22:04 Re: Zugriffe auf Datenstruktur synchronisieren

braunbär hat geschrieben:
Warf hat geschrieben:Verwende kein Boolean Feld. Zugriffe auf booleanfelder sind nicht atomar.

Wie kommst du darauf?
boolean.PNG

Viel atomarer als ein movb oder ein cmpb (s.Anhang) geht nicht. Ob auf der untersten Hardwareebene ein paar zusätzliche Taktzyklen gebraucht werden, um ein einzelnes Byte zu schreiben (beim lesen nicht einmal das), ist für den Programmablauf egal. Da fährt kein Interrupt dazwischen.


Der assembler hat nichts zu sagen, critical sections sind per definition atomar, während boolean Felder auf Compiler und Architektur ankommen. Mit der neuen Fpc version könnte das schon ganz anders aussehen. Bei nicht definiertem verhalten darf man niemals Annahmen über das Verhalten machen.

PS: critical sections machen übrigens ein Check und set in einer Aktion, also ein der Wert wird ausgelesen, ein neuer wert wird geschrieben, in einem Tick. Ohne diese atomare Eigenschaft kann zwischen dem get und dem set unterbrochen werden, und dann denken beide Threads die Variable wäre nicht gesetzt und führen gleichzeitig Code aus. Mit critical sections ist das per Definition unmöglich, unabhängig von compilerverhalten, Optimierung oder Architektur
Warf
 
Beiträge: 589
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Michl » 30. Aug 2017, 22:58 Re: Zugriffe auf Datenstruktur synchronisieren

@Warf: Wenn ich mir die Ausgangsfragestellung ansehe, wird mit Timern in einer Single-Thread-Anwendung gearbeitet. Daher wären hier keinerlei CriticalSections etc. nötig.
Code: Alles auswählen
type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 
Michl
 
Beiträge: 2168
Registriert: 19. Jun 2012, 11:54
OS, Lazarus, FPC: Win7 Laz 1.7 Trunk FPC 3.1.1 Trunk | 
CPU-Target: 32Bit/64bit
Nach oben

Beitragvon braunbär » 30. Aug 2017, 23:10 Re: Zugriffe auf Datenstruktur synchronisieren

Ich habe mir jetzt dein Programm angeschaut, da sind gibt es zumindest noch zwei Probleme.

1.
Es wäre wirklich gescheiter, das fertige TList zu verwenden, statt mit next-Pointern Eigenbau-Listen zu produzieren, mit denen du dir dann Eigentore schießt. :wink:
Mit Liste.Loesche(index) in timer1timer wird das Listenelement gelöscht und Index auf das nächste Element gesetzt. Gleich darauf kommt noch einmal index:=index.next, d.h. das Element, das hinter deinem gelöschten Element in der Liste sitzt, wird übersprungen. In diesem konkreten Fall wohl nicht sehr schlimm, aber prinzipiell ein unnötiger Fehler. Wenn du eine TList ganz einfach in einer for-Schleife vom letzten zurück zum 1. Element durchläufst (bitte nicht von vorne nach hinten, weil da werden beim Löschen eines Elements die Indizes der noch nicht bearbeitenden Elemente verändert), die Elemente löschst, die gelöscht gehören, und neue Elemente hinten an die Liste anhängst, fällt in deinem Programm ein Gutteil fehlerträchtiger Code weg.

Ich sehe gerade, dass dein Lösch-Code auskommentiert ist und du das Element momentan nur unsichtbar machst. Keine gute Idee. Den Fehler bekommst du aber natürlich erst, wenn du die Kreise richtig löschst, was ja auch gemacht gehört.

2.
Dadurch, dass du Listenelemente in einer asynchronen Timerroutine löschst, kann dir in extrem seltenen Fällen passieren, dass genau dann timer1timer aufgerufen wird, wenn gerade die Mousedown Routine gestartet wurde (also genau zwischen begin und self.clicked:=true) - nicht sehr wahrscheinlich, aber möglich. Wenn dann in der Timerroutine das Element gelöscht wird, auf das du geklickt hast, dann knallt es, weil wenn das Programm von der Timerroutine zurückkommt und self.clicked:=true ausführen will, dann gibt es dieses self nicht mehr.
Nachdem aber bei einem Klick auf ein Element, das in der Zwischenzeit ohnehin schon gelöscht worden ist, nichts mehr zu tun ist, wird es in dem Fall genügen, das self.clicked:=true mit einem Try except zu kapseln, um so die ausgelöste Exception zu ignorieren und einfach weiter zu machen.
braunbär
 
Beiträge: 162
Registriert: 8. Jun 2017, 17:21

Beitragvon braunbär » 30. Aug 2017, 23:21 Re: Zugriffe auf Datenstruktur synchronisieren

Der assembler hat nichts zu sagen, critical sections sind per definition atomar, während boolean Felder auf Compiler und Architektur ankommen.
Mit der neuen Fpc version könnte das schon ganz anders aussehen. Bei nicht definiertem verhalten darf man niemals Annahmen über das Verhalten machen.

Zum einen würde mich ein Link dazu interessieren.
Zum anderen wäre das in diesem Fall auch egal, weil auf der einen Seite nur gelesen, und auf der anderen nur geschrieben wird.
Entweder das Schreiben passiert "rechtzeitig", dann wird der Click in der aktuellen Schleife verarbeitet, oder es kommt zu spät, dann eben in der nächsten Schleife. Passieren kann nichts. Nebenbei würde ich die Wahrscheinlichkeit, dass in einer zukünftigen FPC Version das atomare movb bzw. cmpb durch irgend etwas anderes ersetzt wird, auf eine glatte Null einschätzen.

Delphi produziert hier übrigens auch ganz atomaren Code. Zumindest Delphi 5 und das RAD Studio 10.1 Berlin. Turbo Pascal 3 ebenfalls. :D
Die Versionen dazwischen habe ich nicht gecheckt.

PS: critical sections machen übrigens ein Check und set in einer Aktion, also ein der Wert wird ausgelesen, ein neuer wert wird geschrieben, in einem Tick. Ohne diese atomare Eigenschaft kann zwischen dem get und dem set unterbrochen werden, und dann denken beide Threads die Variable wäre nicht gesetzt und führen gleichzeitig Code aus. Mit critical sections ist das per Definition unmöglich, unabhängig von compilerverhalten, Optimierung oder Architektur

Das ist schon klar, hat aber mit der Aufgabenstellung hier nichts zu tun.
braunbär
 
Beiträge: 162
Registriert: 8. Jun 2017, 17:21

Beitragvon wp_xyz » 31. Aug 2017, 00:09 Re: Zugriffe auf Datenstruktur synchronisieren

mintpc hat geschrieben:Die Version von wp_xyz funktioniert leider nicht. Vielleicht ist da von mir ein Umsetzungsfehler drin, aber es kommen recht schnell wieder die Zugriffsfehler.

Ja, da hast du auch eine recht abenteuerliche Konstruktion hingelegt: Im MouseDown des Kreises sagt dieser der Liste: "Kille mich", was die Liste natürlich sofort erledigt. Aber der Kreis hat noch eine Menge zu erledigen - das MouseDown-Ereignis ist nur der Anfang mehrerer Aktionen, die im Zusammehang mit dem Klick ablaufen. Den Kreis gibt es aber dann schon nicht mehr.
wp_xyz
 
Beiträge: 2184
Registriert: 8. Apr 2011, 08:01

» Weitere Beiträge siehe nächste Seite »
Nächste

Zurück zu Freepascal



Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste

porpoises-institution
accuracy-worried