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

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
ore12
Beiträge: 19
Registriert: Fr 3. Feb 2012, 16:37

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

Beitrag von ore12 »

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 Do 16. Jan 2020, 06:03, insgesamt 1-mal geändert.

Benutzeravatar
theo
Beiträge: 8334
Registriert: Mo 11. Sep 2006, 19:01

Re: Properties und Variablen von TThread-Abkömmlingen

Beitrag von theo »

Du hast falsch deklariert:

Code: Alles auswählen

var
  MyThread: TThread;

müsste

Code: Alles auswählen

 
var
  MyThread: TMyThread;

heissen

ore12
Beiträge: 19
Registriert: Fr 3. Feb 2012, 16:37

Re: Properties und Variablen von TThread-Abkömmlingen

Beitrag von ore12 »

Danke! Manchmal sieht man das Offensichtliche auch bei 100x draufschauen einfach nicht...

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

Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Beitrag von Warf »

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

Socke
Lazarusforum e. V.
Beiträge: 2748
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: [gelöst] Properties und Variablen von TThread-Abkömmling

Beitrag von Socke »

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

ore12
Beiträge: 19
Registriert: Fr 3. Feb 2012, 16:37

Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Beitrag von ore12 »

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?

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

Re: [gelöst] Properties und Variablen von TThread-Abkömmling

Beitrag von Warf »

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

Antworten