Multitasking die 1.: CriticalSection

Für Fragen von Einsteigern und Programmieranfängern...
Nimral
Beiträge: 44
Registriert: Mi 10. Jun 2015, 11:33

Multitasking die 1.: CriticalSection

Beitrag von Nimral »

Hi allseits,

ich beziehe mich auf das Beispielprojekt examples\waitforexample1.

in der Prozedur TBaseThread.Log setzt der Programmierer eine CriticalSection ein. Er muss das m.E. machen, weil er den Text, den er anhängen möchte, erst in einen String in Form1 (MsgText) schreibt, den er dann erst per Form1.AddMessage hinzufügt. Damit der String sicher ankommt, muss die Code-Sequenz die ihn setzt und dann dem Memo hinzufügt geschützt werden. So weit, so klar.

Ich bin mir gerade unsicher, ob das nur eine Demo ist, oder ob es gute Gründe gibt, warum das generell besser so gemacht werden soll. Immerhin hätte er Form1.Addmessage ja auch so schreiben können, dass er den String als Parameter übergibt, und damit m.E. das Problem nicht gehabt und die CriticalSection nicht gebraucht. Ich hätte es so gemacht, ohne eine Sekunde darüber nachzudenken, diese m.E. (in der Demo) unnütze MsgText Variable einzuführen, mit der ich mir dann Race Probleme zwischen den Threads einhandle, die ich mit der CriticalSection wieder wegbekommen muss.

Ist das also nur der Demo halber so programmiert, oder hatte der Autor der Demo einen guten Grund, über die Zwischenvariable zu gehen statt Form1.AddMessage bzw. sogar Form1.Memo.Add direkt zu bemühen?

Thnx, Armin.

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

GUI-relevante Komponenten (z.B. Form....) dürfen in WorkerThreads nicht angepackt werden.
-Michael

Nimral
Beiträge: 44
Registriert: Mi 10. Jun 2015, 11:33

Re: Multitasking die 1.: CriticalSection

Beitrag von Nimral »

mschnell hat geschrieben:GUI-relevante Komponenten (z.B. Form....) dürfen in WorkerThreads nicht angepackt werden.
-Michael


Danke für die Antwort! Und gleich tauchen weitere Fragen auf.

Nur um des Lernens und Verstehens willen, warum nicht, und was passiert, wenn man es trotzdem tut?

Ich kenne den Effekt, dass direkte Zugriffe auf GUI Elemente keine (sofortige) Wirkung zeigen, so lange man nicht noch einen Application.Processmessages nachschiebt ... aus Threads heraus ist das also auch keine Option mehr?

Bisher arbeite ich gerne mit Synchronize. Ich hätte also auch sowas machen können?

Code: Alles auswählen

 
Synchronize(Memo1.Lines.Add(S));
 


Damit wäre der Befehl immerhin im Kontext des GUI Threads gelandet und im Sinn Deiner Aussage zulässig, aber wenn ich das richtig verststanden habe läuft der Worker-Thread danach weiter, und der GUI Thread arbeitet den Aufruf ab wenn er mal wieder dran ist - wie wird sicher gestellt, dass S dann überhaupt noch existiert?

Armin.

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Nimral hat geschrieben:warum nicht, und was passiert, wenn man es trotzdem tut?

Aktivitäten (Funktionen, Properties) bei GUI Komponenten können das angebundene Widget-Set im Betriebssystem aufrufen und das ist nicht threadfest. Das Widget-Set in einem anderen Thread aufzurufen kann zu beliebigen Ergebnissen führen inklusive Total-Absturz des Programms.
Nimral hat geschrieben:Bisher arbeite ich gerne mit Synchronize. Ich hätte also auch sowas machen können?

Genau dafür gibt es TThread.Synchronize (wenn der Worker-Thread warten soll, bis die (GUI-) Aktivitäten im Main Thread beendet sind) und TThread.Queue (wenn der Worker Thread parallel weiter laufen soll, was z.B. in diesem Fall, wenn nur einfach etwas in der GUI angezeigt werden soll, ideal ist).
Nimral hat geschrieben:[wie wird sicher gestellt, dass S dann überhaupt noch existiert?

Strings arbeiten (anders als Klassen) mit einem threadfesten Reference-Counting. Bei Synchronize wird das sicher klappen, weil der String ja erst aus dem Fokus gerät, wenn die Mainthread-Aktivität fertig ist. Bei Queue darf man es nicht so machen. Einerseits bin ich nicht sicher, ob das Reference counting dann korrekt funktioniert und andererseit kann der Worker-Thread den String ja auch neu beschreiben, bevor der Mainthread ihn ausgegeben hat. Da sollte man eine TThreadlist mit allokierten Speicherblöcken verwenden, oder eine TStringlist, die man in eine Critical Section packt.

-Michael
Zuletzt geändert von mschnell am Do 23. Jan 2020, 10:41, insgesamt 1-mal geändert.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 4138
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Niederösterreich
Kontaktdaten:

Re: Multitasking die 1.: CriticalSection

Beitrag von af0815 »

mschnell hat geschrieben:
Nimral hat geschrieben:warum nicht, und was passiert, wenn man es trotzdem tut?

Aktivitäten (Funktionen, Proerties) bei GUI Komponenten können das angebundene Widget-Set im Betriebssystem aufrufen und das ist nicht threadfest. Das Widget-Set in einem anderen Thread aufzurufen kann zu beliebigen Ergebnissen führen inklusive Total-Absturz des Programms.

Bei gtk2 erhält man unter Umständen kryptische Fehlermeldungen, das Handels oder Objekte ungültig sind. Ist mir unlängst passiert, da ich bei einem Callback einer Komponente übersehen habe das da ein Threadwechsel vorgekommen ist. Erst wie ich das Programm aus der Kommandozeile gestartet habe, ist mir die Fehlermeldung aufgefallen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Nimral
Beiträge: 44
Registriert: Mi 10. Jun 2015, 11:33

Re: Multitasking die 1.: CriticalSection

Beitrag von Nimral »

Hi Michael,

danke für die Klarstellungen, Du hast mir sehr weiter geholfen bei meinem aktuellen Projekt. An sich läuft es, aber ich hab noch ein paar Tage übrig im Budget und überlege, ob ich den Code nicht noch verbessern kann, möge es dem nächsten Projekt zugute kommen.

Was mich z.B. derzeit am meisten stört ist, dass, egal wie ich mit Synchronize, Queue oder Events umgehe, immer der Worker-Thread direkte Verweise auf entweder die GUI oder die Threads welche sonst noch was von ihm wollen könnten, beinhalten muss. Noch schöner (wenigstens auf dem Papier) wäre für meine Anwendung eine Architektur, bei welcher ein Worker-Thread, wenn er ein brauchbares Ergebnis hat (einen Messwert empfangen und ggf bearbeitet), diesen einfach irgendwie in die Luft katapultiert in Form einer "Message", und alle anderen Threads, die sich dafür interessieren, können damit machen was sie wollen. Das Messgerät ist ja nicht wirklich interessiert daran, ob seine Messwerte überhaupt von jemandem ausgewertet werden, und andererseits wäre das auch effizient, weil man so mit einer einzigen Message beliebig viele Threads mit einem Messwert versorgen könnte. Da alle Messgeräte ausschließlich relativ kleine Zahlen- oder kurze Status-Strings liefern, könnte der Mess-bzw. Statuswert als "Payload" direkt an der Message hängen.

Ich lese mich deshalb derzeit auch in Kommandos ein, die "Messages" auswerfen, also Sendmessage, Postmessage und Konsorten. Aber das ist eine andere Baustelle.

HG aus Bayern,

Armin.

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Ich mache das meistens mit (von mir so genannten) "Kommunikations-Objekten".
Ich definiere eine Klasse, die die Daten, die ich vom WorkerThread zum Mainthread senden will enthält (z.B. "TransportObject").
Außerdem enthält die Klasse eine Procedur z.B. "Transport".

im WorkerThread mache ich dann

Code: Alles auswählen

 
  Transport := TransportObject.Create;
  Transport.Datenxyz := ......;
  ....
  MyThread.Queue(TransportObjekt.Transport);
 

Danach wird die Vatiable Transport nie wieder verwendet (!!!).


TransportObject.Transport gibt in entsprechender Form einen im Workerthread gesammelten Datensatz aus und räumt wenn nötig die in TransportObjekt enthaltenen Datenstrukturen auf. am Schluss steht einfach

Code: Alles auswählen

 
  free;  // letzte Anweisung !!!
end;  // Transport
 

Und das Transportobjekt gibt sich selber frei.

Also wird TransportObjekt.Transport im Mainthread laufen und kann da z.B. Prozeduren oder properties aufrufen und mit Daten versorgen.

Wenn Du die in "Transport" verwendeten Mainthread-relevanten Klassen (oder auch das Tansport-Objekt selber) nicht direkt im Thread definieren willst, kannst Du ja Deiner "TMyThread" Klasse entsprechende Properties geben, die Du im Mainthread-Umfeld nach Kreieren der MyThread Instanz entsprechende Objekte und Callback-Funktionen (z.B. DoTransportCreate) zuweist, die im Thread-Umfeld dann verwendet werden können.

-Michael

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Nimral hat geschrieben:Ich lese mich deshalb derzeit auch in Kommandos ein, die "Messages" auswerfen, also Sendmessage, Postmessage und Konsorten. Aber das ist eine andere Baustelle.

Die Message Sachen sind ein "Relikt" aus Windows.
Da fpc aber auf vielen Betriebssystemen läuft, braucht man es nicht. FPC enthält entsprechende "allgemeine" Funktionalitäten im "Sprachumfang".
"Message", "Procedure message", etc verwendet 1:1 die Windows API, wenn das Programm auf Windows läuft und in der Library realisierte Workalikes, wenn das Programm auf einem anderen Betriebbssystem läuft.
Deshalb würde ich Messsage ertc nur verwenden , wenn ich weiß dass das Programm auf Windows laufen wird und spezielle Windows Funktionalität genutzt werden soll.
-Michael

Warf
Beiträge: 1421
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: MacOS | Win 10 | Linux
CPU-Target: x86_64
Wohnort: Aachen

Re: Multitasking die 1.: CriticalSection

Beitrag von Warf »

mschnell hat geschrieben:Strings arbeiten (anders als Klassen) mit einem threadfesten Reference-Counting. Bei Synchronize wird das sicher klappen, weil der String ja erst aus dem Fokus gerät, wenn die Mainthread-Aktivität fertig ist. Bei Queue darf man es nicht so machen. Einerseits bin ich nicht sicher, ob das Reference counting dann korrekt funktioniert und andererseit kann der Worker-Thread den String ja auch neu beschreiben, bevor der Mainthread ihn ausgegeben hat. Da sollte man eine TThreadlist mit allokierten Speicherblöcken verwenden, oder eine TStringlist, die man in eine Critical Section packt.

-Michael


Strings sind nicht Thread Sicher. Beispiel:

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
 
uses classes;
 
var s: String;
 
 
type
 
  { T }
 
  T = class(TThread)
  protected
    procedure Execute; override;
  end;
 
{ T }
 
procedure T.Execute;
var
  i: Integer;
begin
  for i:=0 to 1000000 do
  begin
    if length(s) > 100 then s := '';
    s := s + 'a';
  end;
end;
 
var
  t1, t2: T;
begin
  t1 := T.Create(false);
  t2 := T.Create(false);
  t1.WaitFor;
  t2.WaitFor;
  WriteLn(s);
  ReadLn;
end.
 


Wirft Seg-Faults, weil der durch parallelen zugriff während dem Referenzzählen, der String von einem Thread gelöscht wird, bevor der Referenzzähler vom anderen Thread inkrementiert werden kann

mschnell hat geschrieben:Die Message Sachen sind ein "Relikt" aus Windows.
Da fpc aber auf vielen Betriebssystemen läuft, braucht man es nicht. FPC enthält entsprechende "allgemeine" Funktionalitäten im "Sprachumfang".
"Message", "Procedure message", etc verwendet 1:1 die Windows API, wenn das Programm auf Windows läuft und in der Library realisierte Workalikes, wenn das Programm auf einem anderen Betriebbssystem läuft.
Deshalb würde ich Messsage ertc nur verwenden , wenn ich weiß dass das Programm auf Windows laufen wird und spezielle Windows Funktionalität genutzt werden soll.
-Michael

Gibt das Konzept von Message Queues auch im POSIX standard, gibt soweit ich weiß nur keine einheitliche (fpc) implementierung, da sich Windows und Posix (obwohl das konzept gleich ist) doch sehr unterscheiden.

Was man auch benutzen kann ist der IPC client, der ist Thread Safe, und wenn du willst kannst du damit deine Threads sogar in verschiedene Prozesse auslagern. Damit kannst du tatsächlich ne ganze menge Probleme vermeiden, weil du dann keine sorgen mehr um Synchronize, Critical Sections, Deadlocks, etc machen musst. Dafür hast du halt einen recht eingeschränkten kommunikationsweg (nur einen Stream)

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Das finde ich aber enttäuschend !
Dass das Reference Counting Pobleme machen könnte hatte ich in Erwägung gezogen. Das geht bei einem statisch angelegn String (der ja immer im Fokus ist) aber doch wohl nie auf Null. (Oder doch durch mangelnde Threadfestigkeit ???)
Aber "normale" String Aktivitäten ?!?!?!?!? Die Ergebnisse können bei konkurrierendem Zugriff fragwürdig sein aber Seg-Faults dürften nicht auftreten.

- Ist das dokumentiert ?

- Ist das bei Delphi auch so ?

-Michael

Socke
Lazarusforum e. V.
Beiträge: 2733
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Multitasking die 1.: CriticalSection

Beitrag von Socke »

mschnell hat geschrieben:Das geht bei einem statisch angelegn String aber doch wohl nie auf Null.

Konstante Strings werden nicht referenzgezählt und haben eine konstante (negative?) RefCount.

mschnell hat geschrieben:Aber "normale" String Aktivitäten ?!?!?!?!? Die Ergebnisse können bei konkurrierendem Zugriff fragwürdig sein aber Seg-Faults dürften nicht auftreten.

Es geht immer um die Fälle des Dereferenzierens. D.h. reine Leseoperationen sind unkritisch.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Warf hat geschrieben:Was man auch benutzen kann ist der IPC client,

Meist reicht aber in Richtung Mainthread TThread.Queue und TThread.Synchronize und in Richtung Thread TEvent oder TCriticalSection zur Synchronisation.
-Michael

Warf
Beiträge: 1421
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: MacOS | Win 10 | Linux
CPU-Target: x86_64
Wohnort: Aachen

Re: Multitasking die 1.: CriticalSection

Beitrag von Warf »

Socke hat geschrieben:Es geht immer um die Fälle des Dereferenzierens. D.h. reine Leseoperationen sind unkritisch.


Nur wenn niemand schreibt, also praktisch String Konstanten sind sicher. Sobald es einen schreibenden Zugriff gibt, kann auch jeder Lesende Zugriff knallen

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
 
uses classes;
 
var s: String;
var sum: Integer;
 
type
 
  TWriteThread = class(TThread)
  protected
    procedure Execute; override;
  end;
 
  TReadThread = class(TThread)
  protected
    procedure Execute; override;
  end;
 
procedure TReadThread.Execute;
var
  j, k: Integer;
  str: String;
begin
  for j:=0 to 1000000 do
  begin
    str := s;
    for k:=1 to length(str) do
      sum := sum + ord(str[k]);
  end;
end;   
 
procedure TWriteThread.Execute;
var
  i: Integer;
begin
  for i:=0 to 1000000 do
  begin
    if length(s) > 100 then s := '';
    s := s + 'a';
  end;
end;
 
var
  t1: TWriteThread;
  t2: TReadThread;
begin
  t1 := TWriteThread.Create(false);
  t2 := TReadThread.Create(false);
  t1.WaitFor;
  t2.WaitFor;
  WriteLn(s);
  ReadLn;
end.
 


mschnell hat geschrieben:Das finde ich aber enttäuschend !
Dass das Reference Counting Pobleme machen könnte hatte ich in Erwägung gezogen. Das geht bei einem statisch angelegn String (der ja immer im Fokus ist) aber doch wohl nie auf Null. (Oder doch durch mangelnde Threadfestigkeit ???)
Aber "normale" String Aktivitäten ?!?!?!?!? Die Ergebnisse können bei konkurrierendem Zugriff fragwürdig sein aber Seg-Faults dürften nicht auftreten.

- Ist das dokumentiert ?

- Ist das bei Delphi auch so ?

-Michael

Doch das geht auf null. Die Operation s := s + 'a'; kann man schreiben als s2 := s + 'a'; s := s2; und dabei wird der alte string überschrieben und damit geht die Referenzzahl auf 0.

Dokumentiert ist das glaube ich nicht (direkt). Soweit ich weiß ist die allgemeine Annahme das nix thread safe ist, und dazu gehören auch Strings. Ist natürlich verdammt doof da Referenzzählung selbst nicht so trivial ist, und man halt schon recht gut wissen muss was da abgeht, damit man nicht in diese probleme rennt (wie gesagt, selbst lesender zugriff kann zu problemen führen).

Wie der Stand in Delphi ist kann ich nicht sagen, da Delphi meinen Lizensschlüssel anscheinend nicht mehr akzeptieren will

Ich bin auch etwas enttäuscht, denn strings (oder auch arrays) sollten mMn. einfach funktionieren, und die Referenzzählung könnte schon Thread Sicher sein. Was Threading angeht ist der FPC leider ein gutes Stück hinter anderen Programmiersprachen. Java hat z.B. synchronized Methoden (die dann einfach das Objekt zu dem die Methode locken solang sie ausgeführt wird) und selbst C++ hat Thread Sichere standardobjekte. Z.B. shared_ptr ist die C++ implementierung von Referenzzählung, und die ist thread sicher gemacht.

Socke
Lazarusforum e. V.
Beiträge: 2733
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Multitasking die 1.: CriticalSection

Beitrag von Socke »

In Delphi ist der Stand wohl der selbe: https://stackoverflow.com/questions/263 ... nvironment

In dem dort verlinkten Beitrag (https://stackoverflow.com/questions/197 ... e-property) ist auch ein FPC-fähiges Generic zum sicheren Zugriff auf Variablen enthalten (TThreadsafe).
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mschnell
Beiträge: 3408
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: Multitasking die 1.: CriticalSection

Beitrag von mschnell »

Scheint wohl ein generell schwieriges Problem zu sein. In C# und vielen anderen Sprachen sind Strings "reaonly": wenn man einen String verändern will, muss man immer einen neuen machen.
-Michael

Antworten