14

I'm writing a multi-threaded application in Delphi and need to use something to protect shared resources.

In C# I'd use the "lock" keyword:

private someMethod() {
    lock(mySharedObj) {
        //...do something with mySharedObj
    }
}

In Delphi I couldn't find anything similar, I found just TThread.Synchronize(someMethod) method, which prevents potential conflicts by calling someMethod in main VCL thread, but it isn't exactly what I want to do....

Edit: I'm using Delphi 6

Ondra C.
  • 2,302
  • 3
  • 31
  • 35

5 Answers5

17

(Un)fortunately you cannot lock on arbitrary objects in Delphi 6 (although you can in more recent versions, 2009 and later), so you need to have a separate lock object, typically a critical section.

TCriticalSection (note: the documentation is from FreePascal, but it exists in Delphi as well):

Example code:

type
  TSomeClass = class
  private
    FLock : TCriticalSection;
  public
    constructor Create();
    destructor Destroy; override;

    procedure SomeMethod;
  end;

constructor TSomeClass.Create;
begin
  FLock := TCriticalSection.Create;
end;

destructor TSomeClass.Destroy;
begin
  FreeAndNil(FLock);
end;

procedure TSomeClass.SomeMethod;
begin
  FLock.Acquire;
  try
    //...do something with mySharedObj
  finally
    FLock.Release;
  end;
end;
David
  • 12,749
  • 6
  • 61
  • 126
Lasse V. Karlsen
  • 350,178
  • 94
  • 582
  • 779
  • 1
    Delphi 2009 introduced the ability to get a lock on any object - see "Why Has the Size of TObject Doubled In Delphi 2009?" at http://blogs.teamb.com/craigstuntz/2009/03/25/38138/ – mjn Nov 21 '10 at 16:22
  • 2
    Yeah, I learned about that sometime later but the question had already been edited to mention delphi 6 so I didn't bother updating my answer just to bump it back on the front page. I should've left a comment though. But, I will make a note to say that I take opposition to the view in that article. The ability to lock on any object is *not* a good thing, and can easily lead to deadlocks if you don't know what you're doing. Since anyone can lock on any object, sometimes they do. It is much better to specifically allocate locking objects and use those. No surprises (in that regard.) – Lasse V. Karlsen Nov 23 '10 at 08:57
11

There is no equivalent in Delphi 6. As of Delphi 2009, you can use the System.TMonitor methods to grab locks on arbitrary objects.

System.TMonitor.Enter(obj);
try
  // ...
finally
  System.TMonitor.Exit(obj);
end;

(You need the "System" prefix because the TMonitor name conflicts with the type in the Forms unit. The alternative is to use the global MonitorEnter and MonitorExit functions.)

Rob Kennedy
  • 156,531
  • 20
  • 258
  • 446
3

Although not entirely as easy as c#, following might work for you.

  with Lock(mySharedObj) do
  begin
    //...do something with mySharedObj
    UnLock;
  end;

In a nutshell

  • a list is kept for every instance you wish to protect.
  • when a second thread cals the Lock(mySharedObj), the internal list will be searched for an existing lock. A new lock will be created if no existing lock is found. The new thread will be blocked if another thread still has the lock.
  • the Unlock is necessary because we can not be sure that the reference to the ILock instance only will get out of scope at the end of the method calling Lock. (If we could, the Unlock could be removed).

Note that in this design, one TLock gets created for every object instance you wish to protect without it being freed until the application terminates.
This could be factored in but it would involve messing around with _AddRef & _Release.


unit uLock;

interface

type
  ILock = interface
    ['{55C05EA7-D22E-49CF-A337-9F989006D630}']
    procedure UnLock;
  end;

function Lock(const ASharedObj: TObject): ILock;

implementation

uses
  syncobjs, classes;

type
  _ILock = interface
    ['{BAC7CDD2-0660-4375-B673-ECFA2BA0B888}']
    function SharedObj: TObject;
    procedure Lock;
  end;

  TLock = class(TInterfacedObject, ILock, _ILock)
  private
    FCriticalSection: TCriticalSection;
    FSharedObj: TObject;
    function SharedObj: TObject;
  public
    constructor Create(const ASharedObj: TObject);
    destructor Destroy; override;
    procedure Lock;
    procedure UnLock;
  end;

var
  Locks: IInterfaceList;
  InternalLock: TCriticalSection;

function Lock(const ASharedObj: TObject): ILock;
var
  I: Integer;
begin
  InternalLock.Acquire;
  try
    //***** Does a lock exists for given Shared object
    for I := 0 to Pred(Locks.Count) do
      if (Locks[I] as _ILock).SharedObj = ASharedObj then
      begin
        Result := ILock(Locks[I]);
        Break;
      end;

    //***** Create and add a new lock for the shared object
    if not Assigned(Result) then
    begin
      Result := TLock.Create(ASharedObj);
      Locks.Add(Result);
    end;
  finally
    InternalLock.Release;
  end;
  (Result as _ILock).Lock;
end;

{ TLock }

constructor TLock.Create(const ASharedObj: TObject);
begin
  inherited Create;
  FSharedObj := ASharedObj;
  FCriticalSection := TCriticalSection.Create;
end;

destructor TLock.Destroy;
begin
  FCriticalSection.Free;
  inherited Destroy;
end;

procedure TLock.Lock;
begin
  FCriticalSection.Acquire;
end;

function TLock.SharedObj: TObject;
begin
  Result := FSharedObj;
end;

procedure TLock.UnLock;
begin
  FCriticalSection.Release;
end;

initialization
  Locks := TInterfaceList.Create;
  InternalLock := TCriticalSection.Create;

finalization
  InternalLock.Free;
  Locks := nil

end.
Lieven Keersmaekers
  • 53,391
  • 11
  • 100
  • 140
  • the Locks list would also need to be somehow sorted and the "locate" done through binary search for performance improvement. – Ken Bourassa Jun 11 '10 at 16:55
  • @Ken Bourassa: True, there is much room left for improvement. The objective though was just to show how a similar construction as used in c# could be done with Delphi 6. – Lieven Keersmaekers Jun 11 '10 at 18:29
1

As said, for short code, which doesn't call outside the local scope and doesn't acquire any other locks, you can use critical sections via SyncObjs.TCriticalSection,
for longer/more complicated code you may use SyncObjs.TMutex, which is waitable (with timeout), doesn't stall if the owning thread dies and can be shared by-name with another processes.
Using these wrappers makes changes to the synchronization layer easier.

In all cases, beware of dragons here: my answer to Difference between the WaitFor function for TMutex delphi and the equivalent in win32 API

Community
  • 1
  • 1
Viktor Svub
  • 1,421
  • 9
  • 15
0

Using class helpers you can use this. Will not work with older versions though. But I would advise using TMonitor only in XE5. Since its quite a lot slower than TRTLCriticalSection.

http://www.delphitools.info/2013/06/06/tmonitor-vs-trtlcriticalsection/

THelper = class helper for TObject
  procedure Lock;
  procedure Unlock;
end;

procedure THelper.Lock;
begin
  System.TMonitor.Enter(TObject(Self));
end;

procedure THelper.Unlock;
begin
  System.TMonitor.Exit(TObject(Self));
end;
Eric Santos
  • 131
  • 2
  • 10