5

In the delphi source code we have :

class function TNetEncoding.GetBase64Encoding: TNetEncoding;
var
  LEncoding: TBase64Encoding;
begin
  if FBase64Encoding = nil then
  begin
    LEncoding := TBase64Encoding.Create;
    if AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) <> nil then
      LEncoding.Free
{$IFDEF AUTOREFCOUNT}
    else
      FBase64Encoding.__ObjAddRef
{$ENDIF AUTOREFCOUNT};
  end;
  Result := FBase64Encoding;
end;

but I don't understand, they mix Atomic operation (AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) with non atomic operation like if FBase64Encoding = nil then and Result := FBase64Encoding;

Is it not a mistake ?

zeus
  • 12,173
  • 9
  • 63
  • 184
  • 5
    No it's not a mistake. Its a variation on double checked locking. But instead of locking you allow threads to speculatively create the singleton. If two threads create the object, only the first one succeeds and the second one destroys their new object. – David Heffernan Jan 09 '19 at 07:39
  • @DavidHeffernan: but if one thread is doing AtomicCmpExchange(Pointer(FBase64Encoding), Pointer(LEncoding), nil) and AT THE EXACT SAME MOMENT another is doing if FBase64Encoding = nil AND at this moment only half of the bytes of the pointer are written to FBase64Encoding (on 64bits, say that only 4 bytes are written of the total of 8 bytes used by a pointer) so FBase64Encoding will be not nil BUT will not point the the good location either ? – zeus Jan 09 '19 at 08:22
  • That explains your QP report about CocoaPointerConst :-) – Dave Nottage Jan 09 '19 at 08:59
  • @DaveNottage yes :) – zeus Jan 09 '19 at 15:35

1 Answers1

12

In the comments you make it clear that your concern is that the unprotected memory operations could tear. By tearing we mean that a read thread reads the variable when it is partially written.

That is a valid concern in general, but tearing cannot happen in this case. The reason being is that aligned memory access is guaranteed not to tear. When the memory operation is aligned, as this one is, a reader cannot read a partially written variable. This is typically guaranteed by the hardware bus serializing all memory access within a single cache line.

So, no, this is not a mistake, the code is correct.

The code itself is used to lazily create a singleton. A common technique to do so in a threadsafe manner is double checked locking. This code uses an alternative technique that avoids locking. Instead, the code potentially allows multiple threads to speculatively create the singleton. If more than one thread succeeds in creating the object, the first one to succeed wins, and the other threads destroy their instances and use the one created by the winner thread.

The lock free approach works well provided that it is benign to create additional instances and then destroy them. But this will not always be the case. For example, it may be too expensive to create multiple copies of the instance. In such cases a lock based approach is better.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • "When the memory operation is aligned, as this one is, a reader cannot read a partially written variable." ... hmmm not sure about it, because it also depends of the size of the memory to read. on int32 aligned variable yes i saw that i can't read partial integer, but is it the same on int64 variable (didn't test it yet) and the same on iOS 64 bit (where pointer is an int64) ? – zeus Jan 09 '19 at 15:50
  • 1
    In fact 64 bit aligned access cannot tear even on 32 bit architecture, and certainly cannot tear on 64 bit architecture – David Heffernan Jan 09 '19 at 16:06