This code
procedure MyThreadTestA(const AStr: string);
Is faster than
procedure MyThreadTestB(AStr: string);
Whilst doing the same work, both pass a pointer.
However version B 'correctly' updates the referencecount of AStr and makes a copy if I change it.
Version A passes just a pointer and only the compiler prevents me from changing AStr.
Version A is not safe if I do dirty tricks in Assembler or otherwise to circumvent the compiler protection, this is well known but...
Is passed AStr by reference as a const parameters thread safe?
What happens if AStr's reference count in some other thread goes to zero and the string is destroyed?
No, such tricks are not thread-safe. Const prevents the add-ref, so changes by another thread will affect the value in unpredictable ways. Sample program, try altering the const in the definition of P:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
TWorker = class(TThread)
public
procedure Execute; override;
end;
var
lock: TCriticalSection;
obj: TObj;
procedure P(const x: string);
// procedure P(x: string);
begin
Writeln('P(1): x = ', x);
Writeln('Releasing obj');
lock.Release;
Sleep(10); // give worker a chance to run
Writeln('P(2): x = ', x);
end;
procedure TWorker.Execute;
begin
// wait until TMonitor is freed up
Writeln('Worker started...');
lock.Acquire;
Writeln('worker fiddling with obj.S');
obj.S := 'bar';
TMonitor.Exit(obj);
end;
procedure Go;
begin
lock := TCriticalSection.Create;
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
lock.Acquire;
TWorker.Create(False);
Sleep(10); // give worker a chance to run and block
P(obj.S);
end;
begin
Go;
end.
But it's not just limited to threads; modifying the underlying variable location has similar effects:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
var
obj: TObj;
procedure P(const x: string);
begin
Writeln('P(1): x = ', x);
obj.S := 'bar';
Writeln('P(2): x = ', x);
end;
procedure Go;
begin
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
P(obj.S);
end;
begin
Go;
end.
To add to Barry's answer: It is definitely thread-safe if the string that got passed came from a local variable inside the callers scope.
In that case that local variable will hold a valid reference and the only way (assuming just valid pascal code, no fiddling around in asm) for that local variable to be changed is if your call returns.
This also includes all cases where the source of the string variable is the result of a function call (including property access, e.g. TStrings.Strings[]) because in this case the compiler has to store the string in a local temp variable.
Thread-safety problems can only result if you are directly passing a string from a location where that string can be changed (by the same or another thread) before your call returns.
Related
In Delphi 10.4, I try to convert a TStream to a string with this code:
function MyStreamToString(aStream: TStream): string;
var
SS: TStringStream;
begin
if aStream <> nil then
begin
SS := TStringStream.Create('');
try
SS.CopyFrom(aStream, 0); // Exception: TStream.Seek not implemented
Result := SS.DataString;
finally
SS.Free;
end;
end else
begin
Result := '';
end;
end;
But in this code line, I get an exception "TStream.Seek not implemented": SS.CopyFrom(aStream, 0);
Why? How can I "heal" this code?
The error means you are passing your function a TStream object that does not implement Seek() at all. Such as if you are passing in an actual TStream object and not a derived object, like TFileStream, TMemoryStream, etc, for instance:
var
Strm: TStream;
begin
Strm := TStream.Create; // <-- ERROR
try
MyStreamToString(Strm);
finally
Strm.Free;
end;
end;
TStream is an abstract base class, it should never be instantiated directly.
In this case, the 32-bit Seek() method in the base TStream class calls the 64-bit Seek() method, but will raise that "Seek not implemented" exception if the 64-bit Seek() has not been overridden. A TStream-derived class must override either the 32-bit Seek() or the 64-bit Seek(), and the overridden method must not call the base TStream method it is overriding.
So, make sure you are passing in a valid stream object to your function.
In this example, in a server service (live 24/24 hours), do I need to free/shrink aBaz for free clean/memory?
function foo(const aBar : string) : boolean;
var
aBaz : string;
begin
aBaz := 'very very long string';
Result := (aBar = aBaz);
aBaz := ''; // shrink `aBaz` for free memory
end;
Update
string var in a class is already free by class destroy or I need to shrink??
Eg.:
class Foo = class
FBar : string;
public
constructor Create; overload;
destructor Destroy; reintroduce;
end;
constructor Foo.Create(const ABar : string);
begin
FBar := ABar;
end;
destructor Foo.Destroy;
begin
FBar := ''; // destructor already free memory or I need to shrink?
end;
in a service (live 24/24 hours) when the run-time library frees the memory?
No, there is no need to free or shrink your string. Two reasons:
This particular string is a string literal. It's not allocated on the heap. The compiler includes a literal copy of that string in your EXE, and when you assign it to aBaz, the variable refers directly to that read-only memory in your EXE file. Nothing gets allocated, so there's nothing to free.
Strings in general are subject to automatic reference counting. When a string variable goes out of scope (which is what happens to aBaz when you reach the end keyword in this function), the string that the variable refers to has its reference count decremented. If the resulting count is zero, then the run-time library frees the memory associated with the string.
The compiler inserts reference-managing code automatically. You don't need to do anything with it.
No, you don't need to free strings on your own. Strings are managed automatically.
aBaz will be automatically freed when foo has finished. The string value will be removed from memory in case there is no other string variable containing the same value.
I want to syncronize threads to access a common variable.
Imagine that I have N threads and each of them can acess a global instance of a variable of type TSyncThreds.
Can I call the methods IncCount, DecCount? Or i will have problem with concurrent threads accessing a same instance of a object?
I just syncronize the access to the FCcounter Variable...
type
TSyncThreads = class
public
FCounterGuard: TCriticalSection;
FCounter: integer;
FSimpleEvent: TSimpleEvent;
constructor Create();
procedure Wait();
procedure IncCounter();
procedure DecCounter();
end;
var
SyncThread: TSyncThreads;
implementation
uses
Math, Windows, Dialogs;
{ TSyncThreads }
procedure TSyncThreads.DecCounter;
begin
FCounterGuard.Acquire;
Dec(FCounter);
if FCounter = 0 then
FSimpleEvent.SetEvent;
FCounterGuard.Release;
end;
procedure TSyncThreads.IncCounter;
begin
FCounterGuard.Acquire;
Inc(FCounter);
FCounterGuard.Release;
end;
constructor TSyncThreads.Create();
begin
FCounter := 0;
FSimpleEvent := TSimpleEvent.Create;
FCounterGuard := TCriticalSection.Create;
FSimpleEvent.ResetEvent;
end;
procedure TSyncThreads.Wait;
var
ret: TWaitResult;
begin
ret := FSimpleEvent.WaitFor(INFINITE);
end;
Yes, your code is fine, if a bit overkill for the task at hand. You're worried about multiple threads accessing the same object, but that's exactly what synchronization objects like TCriticalSection and TEvent are designed for. Imagine how pointless such classes would be if they couldn't be accessed concurrently.
You don't really need a critical section to protect access to your counter. You can use InterlockedIncrement and InterlockedDecrement for more lightweight access. They return the variable's previous value, so if InterlockedDecrement returns 1, then you know it's time to set your event.
If there's an upper bound on the counter, then you might even be able to avoid all of this code and use a semaphore instead. Or you can give each thread its own event to set, and then use WaitForMultipleObjects to wait on all the events; it will return once all the threads have set their events, so you don't have to synchronize access to any shared variable.
I got a thread which takes a db table as a paramater, I got an issue where I can't write to that db table at the same time.
1 instance of TMyThread can have a db table of 'Member' while another could have 'Staff' however there can be cases of two threads open with the same table.
Thus, I need to wrap the code in a critical section (or similar) but I don't want some dirty thing like several crical sections like (fMemberTable, fStaffTable)...
begin
if fDBTable = 'Member' then
fMemberTable.Enter
else if fDbTable = 'Staff' then
....
We have 8 tables so that would get messy
Is there some way to do
TCricalSection(fMemberTable).Enter;
Or some way to do this which is easy to 'scale' and use?
One critical section around the function doesn't make sense, as I don't want to hold back other tables....
You can do:
TMonitor.Enter(fMemberTable);
try
// Do your stuff
finally TMonitor.Exit(fMemberTable);
end;
Please note this is a SPIN LOCK, not a true critical section. Very practical if you're not going to have a lot of collisions, but if threads block each other regularly, you might want to fall back to the critical section. The spin lock is, by definition, a busy-wait lock.
but I'm not sure what version of Delphi introduced this and you don't have version-specific tags.
You can use a Critical Section list, for example, My class defined in this unit:
interface
uses Classes, SyncObjs;
type
{ TCriticalSectionList by jachguate }
{ http://jachguate.wordpress.com }
TCriticalSectionList = class
private
FCSList: TThreadList;
FNameList: TStringList;
function GetByName(AName: string): TCriticalSection;
public
constructor Create();
destructor Destroy(); override;
property ByName[AName: string]: TCriticalSection read GetByName; default;
end;
function CSList: TCriticalSectionList;
implementation
uses SysUtils;
{ TCriticalSectionList }
constructor TCriticalSectionList.Create;
begin
inherited;
FCSList := TThreadList.Create;
FNameList := TStringList.Create;
end;
destructor TCriticalSectionList.Destroy;
var
I: Integer;
AList: TList;
begin
AList := FCSList.LockList;
for I := AList.Count - 1 downto 0 do
TCriticalSection(AList[I]).Free;
FCSList.Free;
FNameList.Free;
inherited;
end;
function TCriticalSectionList.GetByName(AName: string): TCriticalSection;
var
AList: TList;
AIdx: Integer;
begin
AList := FCSList.LockList;
try
AName := UpperCase(AName);
AIdx := FNameList.IndexOf(AName);
if AIdx < 0 then
begin
FNameList.Add(AName);
Result := TCriticalSection.Create;
AList.Add(Result);
end
else
Result := AList[AIdx];
finally
FCSList.UnlockList;
end;
end;
var
_CSList: TCriticalSectionList;
function CSList: TCriticalSectionList;
begin
if not Assigned(_CSList) then
_CSList := TCriticalSectionList.Create;
Result := _CSList;
end;
initialization
_CSList := nil;
finalization
_CSList.Free;
end.
The class basically define a List of critical sections, accesible by "name". The first time you ask for a Critical section of a particular name that critical section is automatically created for you. You must access a single instance of this class, use the provided CSList function.
All critical sections are destroyed when the instance of the list is destroyed, for instance, the "default" instance is destroyed upon application end.
You can write code like this example:
begin
CSList[fDBTable].Enter;
try
DoStuff;
finally
CSList[fDBTable].Leave;
end;
end;
Enjoy.
I've got a almost completed app now and the next feature I want to implement is threading. I chose to go with BeginThread(), although am aware of TThread in delphi. The problem I'm coming across is the structure of BeginThread() call. Normally the line in the program that would call the function I want to be threaded is
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
op is a integer.
The line I've switched it out for to create a thread from it is
BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);
From the little amount of infromation I can find on how to actually use BeginThread() this should be a fine call, however on compling all I get is complier errors regarding the structure of my BeginThread() statement paramenters.
EDIT FOR INFORMATION.
The current procedure that calls CompareFiles is
procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;
begin
if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
begin
op := 3;
if RadioButton7.Checked = True then op := 0;
if RadioButton3.Checked = True then op := 1;
if RadioButton4.Checked = True then op := 2;
if RadioButton5.Checked = True then op := 3;
if RadioButton6.Checked = True then op := 4;
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
end;
end;
If I was to use TThread as suggested by a couple of people, and as displayed by Rob below, I'm confused at how a) I would pass op,Edit3/4.Text and StringGrid2 to the CompareFiles. Guessing from the example of TThread I've seen I thought I would replace the code above with TCompareFilesThread.Executeand the put the current code from Panel29Click into TCompareFilesThread.Create and then add
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
to this
FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;
But I've got this nagging feeling that is totally off the mark.
That's not at all the way to use BeginThread. That function expects a pointer to a function that takes one parameter, but the function you're trying to call wants four. The one parameter you're giving to BeginThread for it to forward to the thread procedure is a string, but you evidently hope that some sort of magic will turn that string of characters into the values that those variables contain.
That's not how Delphi works, and even for the languages that can do something like that, it's generally discouraged to actually do it.
To pass multiple parameters to BeginThread, define a record with all the values you'll need, and also define a record pointer:
type
PCompareFilesParams = ^TCompareFilesParams;
TCompareFilesParams = record
Edit3Text,
Edit4Text: string;
Grid: TStringGrid;
Op: Integer;
end;
Change CompareFiles to accept a pointer to that record:
function CompareFiles(Params: PCompareFilesParams): Integer;
To start the thread, you'll need to allocate an instance of that record and populate its fields:
var
Params: PCompareFilesParams;
begin
New(Params);
Params.Edit3Text := Edit3.Text;
Params.Edit4Text := Edit4.Text;
Params.Grid := StringGrid2;
Params.Op := op;
BeginThread(nil, 0, #CompareFiles, Params, 0, x);
Implement CompareFiles like this so that the record will get freed before the thread terminates:
function CompareFiles(Params: PCompareFilesParams): Integer;
begin
try
// <Normal implementation goes here.>
finally
Dispose(Params);
end;
end;
You can make it all a lot easier if you just use TThread, though. You can make your descendant class have as many parameters as you want in its constructor, so you don't have to mess around with dynamically allocating and freeing a special record.
type
TCompareFilesThread = class(TThread)
private
FEdit3Text,
FEdit4Text: string;
FGrid: TStringGrid;
FOp: Integer;
procedure Execute; override;
public
constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
property ReturnValue;
end;
constructor TCompareFilesThread.Create;
begin
inherited Create(False);
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
FOp := Op;
end;
procedure TCompareFilesThread.Execute;
begin
ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;
Instead of calling BeginThread, you just instantiate the class and let it run:
var
ThreadRef: TThread;
ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);
There's more to using threads, such as knowing when the thread has finished running, but I think you have enough to get started. One last thing to beware of, though, is that TStringGrid is a VCL control. You mustn't do anything with it from this new thread you create (regardless of how you end up creating it). Eveything you do with the grid control need to be done from the main thread. Use TThread.Synchronize and TThread.Queue to shift any VCL operations onto the main thread. Your file-comparing thread will wait for the synchronized operation to complete, but it will keep running without waiting for a queued operation to complete.