[gelöst] Properties und Variablen von TThread-Abkömmlingen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut

[gelöst] Properties und Variablen von TThread-Abkömmlingen

Beitragvon ore12 » 15. Jan 2020, 22:41 [gelöst] Properties und Variablen von TThread-Abkömmlingen

Hi,

bei abgeleiteten Klassen klappt der Zugriff auf Variable und Properties, außer wenn die Klasse von TThread abstammt.

Code: Alles auswählen
program testthrd;
{$mode objfpc}{$H+}
 
uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}
  Classes, SysUtils, SyncObjs;
 
type
 
  TMyThread = class(TThread)
    private
      txt: String;
    protected
      procedure Execute; override;
    public
      property t: String read txt write txt;
  end;
 
  TMyObject = class(TList)
    protected
      txt: String;
    public
      property t: String read txt write txt;
  end;
 
var
  MyThread: TThread;
  MyObject: TMyObject;
 
procedure TMyThread.Execute;
begin
  // dummy
end;
 
begin
  MyObject := TMyObject.Create;
  MyThread := TMyThread.Create(true);
  MyObject.t := 'test'; // <------------das geht
  MyThread.t := 'test'; // <----------- das geht nicht
  //MyThread.Start;
end.             
 
 


Habe ich da einen Denkfehler drin? Wer oder was verhindert, dass ich auf die neu eingeführten Eigenschaften zugreifen kann?
Ich würde dem Thread gerne ein paar Parameter mitgeben, bevor er startet.
Danke für Eure Hinweise!

Gruß

Olaf
Zuletzt geändert von ore12 am 16. Jan 2020, 06:03, insgesamt 1-mal geändert.
ore12
 
Beiträge: 19
Registriert: 3. Feb 2012, 16:37

Beitragvon theo » 15. Jan 2020, 22:52 Re: Properties und Variablen von TThread-Abkömmlingen

Du hast falsch deklariert:

Code: Alles auswählen
var
  MyThread: TThread;

müsste
Code: Alles auswählen
 
var
  MyThread: TMyThread;

heissen
theo
 
Beiträge: 8240
Registriert: 11. Sep 2006, 18:01

Beitragvon ore12 » 16. Jan 2020, 06:03 Re: Properties und Variablen von TThread-Abkömmlingen

Danke! Manchmal sieht man das Offensichtliche auch bei 100x draufschauen einfach nicht...
ore12
 
Beiträge: 19
Registriert: 3. Feb 2012, 16:37

Beitragvon Warf » 17. Jan 2020, 12:08 Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Man sollte aber anmerken das das was du hier machst nicht korrekt ist.

Strings in Free-Pascal sind referenzgezählt, aber nicht thread safe. Das bedeutet wenn du einen String zuweist passiert das:
Code: Alles auswählen
Dec(str^.RefCount);
if str^.RefCount = 0 then
  Free(Str);
str := newStr;
Inc(newStr^.RefCount);


Nehmen wir mal dieses beispiel:
Code: Alles auswählen
// Thread1:
  str := self.txt;
 
// Thread2:
  thread1.txt := str2;

das ist also wenn man inlined
Code: Alles auswählen
// Thread1:
  Dec(str^.RefCount);
  if str^.RefCount = 0 then
    Free(Str);
  str := self.txt; // (1)
  Inc(self.txt^.RefCount); //(3)
 
// Thread2:
  Dec(thread1.txt^.RefCount);
  if thread1.txt^.RefCount = 0 then
    Free(thread1.txt); // (2)
  thread1.txt := str2;
  Inc(str2^.RefCount);


Die beiden Threads laufen jetzt auf der selben CPU. Zu erst läuft Thread1 bis zu (1). Str enthält jetzt eine referenz auf self.txt, aber die referenzzahl ist nicht inkrementiert. Dann schmeißt das OS den thread von der CPU und lädt Thread2 bis mindestens (2) (eventuell auch länger, ab da ists aber kaputt). Angenommen die refcount von thread1.txt war vorher 1. Dann passiert jetzt das folgende. Thread1 hat angefangen den string zu kopieren, kam aber noch nicht dazu die refcount zu erhöhen. Thread2 fängt dann an die refcount zu dekrementieren, sieht die Refcount ist auf 0 gefallen und freed den Speicher. Sobald Thread1 dann mit (3) weitermacht, greift er auf bereitz dealloziierten speicher zu und es kracht
Warf
 
Beiträge: 1330
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: MacOS | Win 10 | Linux | 
CPU-Target: x86_64
Nach oben

Beitragvon Socke » 17. Jan 2020, 14:47 Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Warf hat geschrieben:Man sollte aber anmerken das das was du hier machst nicht korrekt ist.

Die Referenz wird gesetzt, bevor der Thread startet. Solange das Feld MyThread.txt während der Threadlaufzeit nicht durch andere Threads gelesen/geändert wird, ist alles in Ordnung.
Allgemein: Manged-Typen (Ansistrings, UnicodeStrings, Dynamische Arrays, Interfaces mit automatischer Referenzzählung) müssen vor Threadstart eine eigene Referenz erhalten. Während der Thread läuft, darf auf die Variable nur durch den Thread selbst oder in geschütztem Code (Critical Section) zugegriffen werden. Selbst Lesezugriff aus anderen Threads sind verboten, wenn der Thread die Variable ändern kann.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein
Socke
Lazarusforum e. V.
 
Beiträge: 2664
Registriert: 22. Jul 2008, 18:27
Wohnort: Köln
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE | 
CPU-Target: 32bit x86 armhf
Nach oben

Beitragvon ore12 » 17. Jan 2020, 15:31 Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Danke für Eure Hinweise! Ich hatte vor, vor dem Start des Threads ein paar Parameter reinzuladen...

Allgemein: Manged-Typen (Ansistrings, UnicodeStrings, Dynamische Arrays, Interfaces mit automatischer Referenzzählung) müssen vor Threadstart eine eigene Referenz erhalten. Während der Thread läuft, darf auf die Variable nur durch den Thread selbst oder in geschütztem Code (Critical Section) zugegriffen werden. Selbst Lesezugriff aus anderen Threads sind verboten, wenn der Thread die Variable ändern kann.


Eine Frage habe ich aber dazu: warum darf man von aussen keine Lesezugriffe machen auf Variablen, die nur der Thread selbst schreibt?
Was kann da passieren?
ore12
 
Beiträge: 19
Registriert: 3. Feb 2012, 16:37

Beitragvon Warf » 17. Jan 2020, 15:55 Re: [gelöst] Properties und Variablen von TThread-Abkömmling

ore12 hat geschrieben:Danke für Eure Hinweise! Ich hatte vor, vor dem Start des Threads ein paar Parameter reinzuladen...

Allgemein: Manged-Typen (Ansistrings, UnicodeStrings, Dynamische Arrays, Interfaces mit automatischer Referenzzählung) müssen vor Threadstart eine eigene Referenz erhalten. Während der Thread läuft, darf auf die Variable nur durch den Thread selbst oder in geschütztem Code (Critical Section) zugegriffen werden. Selbst Lesezugriff aus anderen Threads sind verboten, wenn der Thread die Variable ändern kann.


Eine Frage habe ich aber dazu: warum darf man von aussen keine Lesezugriffe machen auf Variablen, die nur der Thread selbst schreibt?
Was kann da passieren?


Nur auf managed typen, also Typen die Referenzgezählt sind. Das liegt daran das bei jeder Zuweisung (und ein lesender zugriff kann man sich als zuweisung auf ein temporäres objekt vorstellen) aus 3 schritten besteht: 1. Referenzzähler (vom zu überschreibenden objekt) dekrementieren und objekt eventuell freigeben, 2. Referenz überschreiben 3. Referenzzähler (vom neuen objekt) erhöhen.
Sagen wir du liest das objekt von Thread1 in Thread2 während gleichzeitig Thread1 das objekt überschreibt. Dann kann die Referenzzahl dekrementiert werden bevor Thread2 sie inkrementiert, was dazu führen kann das Thread1 das Objekt löscht bevor er wissen kann das Thread2 es besitzt.

So genannte Race conditions sind aber nicht nur bei gemanageten typen problematisch. Beispiel Integer:
Code: Alles auswählen
// Thread1:
self.x := self.x + 1; // (1)
 
// Thread2:
Thread1.x := 0; // (2)

(1) besteht aus 3 operationen: 1. lesen, 2. Inkrementieren, 3. schreiben. Wenn zwischen 1 und 3 die operation aus (2) ausgeführt wird, sieht das so aus:
Code: Alles auswählen
x  |operation
n  |lese x (thread1)
0  |schreibe x (thread2)
n+1| schreibe x+1 (thread1)

Somit wird die operation von Thread2 einfach "übersprungen" weil Thread1 die änderung nicht mitbekommt.

Nächstes beispiel: Composite typen (records):
Code: Alles auswählen
r: record
  a, b, c: Integer;
end;
 
// Thread1:
self.r := someRecord;
 
// Thread2:
Thread1.r := someOtherRecord;


Das wird dann praktisch zu sowas:
Code: Alles auswählen
// Thread1:
self.r.a := someRecord.a;
self.r.b := someRecord.b;
self.r.c := someRecord.c;
 
// Thread2:
Thread1.r.a := someOtherRecord.a;
Thread1.r.b := someOtherRecord.b;
Thread1.r.c := someOtherRecord.c;


Je nachdem wie die Threads gescheduled werden (also in welcher reihenfolge die ausgeführt werden) kann da alles bei rauskommen, z.B. kann am ende sowas drinstehen:
Code: Alles auswählen
r.a := someOtherRecord.a;
r.b := someRecord.b;
r.c := someOtherRecord.c;


Somit hat man ein gemische von daten, die zu zwei komplett unterschiedlichen Ursprüngen gehören. Bei Composite typen kann das auslesen auch probleme machen:
Code: Alles auswählen
// Thread1:
self.r.a := someRecord.a;
self.r.b := someRecord.b;
self.r.c := someRecord.c;
 
// Thread2:
someOtherRecord.a := Thread1.r.a;
someOtherRecord.b := Thread1.r.b;
someOtherRecord.c := Thread1.r.c;


Jetzt kann in someOtherRecord eine mischung aus dem ursprünglichen Inhalt von Thread1.r und dem neuen inhalt drinstehen.

Langer rede kurzer Sinn, wenn du dir nicht ziemlich sicher bist das es kein Problem gibt (z.b. Booleans sind sicher, Integer nur wenn man keine nichtatomaren funktionen benutzt, Gemanagete typen nie, und records nur wenn es nur aus atomaren feldern besteht die nur einzeln zugegriffen werden) immer locken.

Aber locks immer möglichst klein halten sonst kannst du deadlocks bekommen (also das zwei locks aufeinander warten). Grundsätzlich gilt: Threading ist gar nicht mal so einfach, und man sollte Speicherzugriffe zwischen Threads wenn möglich immer verzichten

Kleines Beispiel:
Code: Alles auswählen
program Project1;
 
{$mode objfpc}{$H+}
 
uses {$IFDEF UNIX}
  cthreads, {$ENDIF}
  Classes;
 
var
  x: integer;
 
type
  TTestThread = class(TThread)
  protected
    procedure Execute; override;
  end;
 
  procedure TTestThread.Execute;
  var
    i: integer;
  begin
    for i := 1 to 1000000 do
      Inc(x);
  end;
 
var
  t1: TTestThread;
  t2: TTestThread;
begin
  t1 := TTestThread.Create(False);
  t2 := TTestThread.Create(False);
  t1.WaitFor;
  t2.WaitFor;
  WriteLn('X: ', x);
  ReadLn;
end

Eigentlich wird x genau 2 millionen mal inkrementiert, das ergebniss ist aber (sehr wahrscheinlich) kleiner als 2 mio, wegen den oben erklärten phenomena. Wenn man in diesem beispiel statt der nicht thread-safen funktion Inc(x), die Thread-Safe funktion InterLockedIncrement(x) benutzt, funktioniert es wunderbar und es kommt 2 mio raus
Warf
 
Beiträge: 1330
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: MacOS | Win 10 | Linux | 
CPU-Target: x86_64
Nach oben

• Themenende •

Zurück zu Freepascal



Wer ist online?

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

porpoises-institution
accuracy-worried