2011年11月1日星期二

TMonitor和TInterlocked

自D2009后TMonitor出现,估计是现在vcl里面用的最多的了,涉及到加锁解锁的地方到处都是。
用TMoitor的好处显而易见,比起SyncObjs单元定义的类(比如TCriticalSection),不用和os内核打交道就把事情办了,这也是向java看齐,和java的关键字synchronized 一样。为了实现TMonitor,Delphi的TObject.InstanceSize从4变成了8(除了VMT的4byte,附加了4byte),这样就让任何类都可以给TMonitor来使用。可以看这里

使用方法异常简单
var
  lock: TObject;
  lock:= TObject.Create;  //定义一加锁变量

begin
  try
    System.TMonitor.Enter(lock);
    ...
  finally
    System.TMonitor.Exit(lock);
  end;
end;
TMonitor的Wait和Pulse以及PulseAll,java的wait, notify, notifyall相当。


提到加锁,就能想到单例模式时常用的一段代码,比如
  TSingleton = class
  strict private
    class var FInstance: TSingleton;
  public
    class function Instance: TSingleton; 
  end;

{ TSingleton }

class function TSingleton.Instance: TSingleton;
begin
  if not Assigned(FInstance) then
    FInstance := TSingleton.Create;
  Result := FInstance;
end;
这段代码其实是有问题的,因为:
判断完是否为nil后,执行下一句之前,可能FInstance有改变。也就是说FInstance可能被赋值了,然后如果再去赋值一次,从而导致泄漏一个TSingleton的实例
要解决这个,1个是用上面的TMonitor给加锁,但是这样岂不是每次调用TSingleton.Instance时都得加锁解锁一回,与理不容。
另一个法子则是用SyncObjs新增的类(XE后加的),TInterlocked。
没这个类之前,要解决这个,又得和os内核打交道,用windows api的InterlockedExchange来完成(Delphi XE前的System.pas里的InterlockedCompareExchangePointer函数没公开出来的)。但是现在有了TInterlocked,就又可以不和os打交道就完成了
代码如下
class function TSingleton.Instance: TSingleton;
begin
  if FInstance = nil then
  begin
    Result := TSingleton.Create;
    if TInterlocked.CompareExchange(Pointer(FInstance), Pointer(Result), nil) <> nil then
      Result.Free;//CompareExchange会返回FInstance之前值,如果不是nil,表示已经被赋值过了,那么就free掉现在新建的实例。
  end;
  Result := FInstance;
end;
查看TInterlocked的代码,可以看到,EMBT没有使用windows api去实现的,而是用汇编,汇编俺早忘光了。
代码如下:
class function TInterlocked.CompareExchange(var Target: Pointer; Value: Pointer; Comparand: Pointer): Pointer;
{$IFDEF X64ASM}
asm
  .NOFRAME
  MOV  RAX,R8
  LOCK CMPXCHG [RCX],RDX
end;
{$ELSE !X64ASM}
{$IFDEF X86ASM}
asm
  XCHG EAX,EDX
  XCHG EAX,ECX
  LOCK CMPXCHG [EDX],ECX
end;
{$ENDIF X86ASM}
{$ENDIF !X64ASM}

和TMonitor类一样,也是靠汇编的LOCK前缀搞定的。
Intel这么解释LOCK
Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted.
所以,用ASM的lock,其实并不是个很好的法子(当然和cpu直接比起和os api打交道,要好非常多的了),因为lock操作冻结了所有cpu的core,这在多core的cpu上几乎无法容忍,Synopse看到这个问题(比如delphi的string,interface的引用计算也是靠LOCK来整的),写了新的SynScaleMM内存管理器,提出了别的实现方法,开源的。但是话又说回来,现在的硬件这么快(今天测试玩HP DL580 G7 Intel Xeon E7-4870,tmd 4个4870cpu,那个快啊),lock就lock呗,FastMM已经工作得非常棒了。

最后,TMonitor的设计和实现是Allen Bauer做的,用得爽没事去溜达他的blog吧。另外要提一下TMonitor是个record,不是class哦,下次再谈谈为啥。

没有评论:

发表评论