Friday, October 29, 2010

More handy classes, part 2

For interlocked functions (atomic operations) I have a special class in BB.Sync:

type

TInterlocked = class
  public
    class function CAS(var aTarget: integer; aCurrentVal, aNewVal: integer): boolean; overload;
    class function CAS(var aTarget: cardinal; aCurrentVal, aNewVal: cardinal): boolean; overload;
    class function CAS(var aTarget: pointer; aCurrentVal, aNewVal: pointer): boolean; overload;
    class function CAS(var aTarget: TObject; aCurrentVal, aNewVal: TObject): boolean; overload;
    class function CAS(var aTarget: LongBool; aCurrentVal, aNewVal: LongBool): boolean; overload;
    class function Inc(var aValue: integer): integer; overload;
    class function Inc(var aValue: int64): integer; overload;
    class function Dec(var aValue: integer): integer; overload;
    class function Dec(var aValue: int64): integer; overload;
    class function Add(var aValue: integer; aCounter: integer): integer;
    class function Sub(var aValue: integer; aCounter: integer): integer;
    class function SetValue(var aTarget: integer; aValue: integer): integer;
  end;
CAS is a acronym of "Compare And Swap", is a must operation in parallel code. Think on a certain class that has an owner thread as the first caller (for whatever reason), the easiest possibility is to use a critical section, but that affects the performance quite a  lot, another possibility is to use CAS():
constructor TLock.Create;
begin
  inherited;

  FCurrentThread := 0; //Nobody owns me
  FDepth := 0;
end;

destructor TLock.Destroy;
begin
  Unlock;

  inherited;
end;

function TLock.IsLocked: boolean;
begin
  result := FCurrentThread <> 0; //Somebody owns me?
end;

function TLock.Lock(aTime: cardinal): boolean;
var
  ticks: Cardinal;

begin
  result := False;

  ticks := GetTickCount;
  repeat
    if TryLock then
    begin
      result := True;
      Break;
    end;

    Sleep(5);
  until GetTickCount - ticks > aTime;
end;

procedure TLock.Lock;
begin
  Lock(INFINITE);
end;

function TLock.TryLock: boolean;
begin
  //The special part of the code
  //It can be translated as
  //
  //ATOMIC ON
  // if FCurrentThread = 0 then
  //    FCurrentThread := GetCurrentThreadId;
  //  Exit(FCurrentThread);
  //ATOMIC OFF
  //
//This can only happens once, so next thread will exit the function 
  //without success
//
  //You could use a critical section here
  //
result := (FCurrentThread = GetCurrentThreadId) or 
(TInterlocked.CAS(FCurrentThread, 0, GetCurrentThreadId));
  if result then
    TInterlocked.Inc(FDepth); //How many times does my owner owns me?
end;

function TLock.Unlock: boolean;
begin
  result := False;
  if FCurrentThread = GetCurrentThreadId then //If caller = owner then release
  begin
    if TInterlocked.Dec(FDepth) = 0 then
    begin
      FCurrentThread := 0; //Now any other thread is able to own me
      result := True;
    end;
  end;
end;
Next post I will talk about ParallelForEach<T>

No comments:

Post a Comment