提升读锁定到写锁定时TMultiReadExclusiveWriteSynchronizer的行为

jpf*_*ius 5 delphi synchronization delphi-2009

我怎样才能实现这样的同步结构:

Lock.BeginRead
try
  if Changed then
    begin
    Lock.BeginWrite;
    try
      Update;
    finally
      Lock.EndWrite;
    end;
    // ... do some other stuff ...
    end;
finally
  Lock.EndRead;
end;
Run Code Online (Sandbox Code Playgroud)

在EndWrite之后不会丢失读锁定,这样在执行此代码块时就不会执行其他编写器.

在这种情况下,Delphi 2009的TMuliReadExclusiveWriteSynchronizer如何表现?

Cha*_*N B 6

看来这个问题包含两个标准:

  • "在EndWrite之后不会丢失读锁定"
  • "执行此代码块时,没有其他编写器可以执行"

我不会进一步讨论第一点,因为其他人已经这样做了.然而,第二点非常微妙,需要解释.

首先,我要说的是我指的是Delphi 2007.我无法访问2009.但是我所描述的行为不太可能发生变化.

您显示的代码确实可以让其他编写器在代码块期间更改该值.当读锁定被提升为写锁定时,读锁定暂时丢失.有一段时间你的线程既没有读锁也没有写锁.这是设计的,因为否则几乎可以肯定死锁.如果提升读锁定的线程实际上在执行此操作时保持读锁定,则可能很容易发生以下情况:

  1. (线程1)获得读锁定
  2. (线程2)获取读锁定(好,共享读锁)
  3. (线程1)获取写锁定(块;线程2具有读锁定)
  4. (线程2)得到写锁定(块;线程1有读锁定 - 现在死锁)

为了防止这种情况,TMuliReadExclusiveWriteSynchronizer在获得写锁定之前释放一些"即时"的读锁定.

(旁注:文章在EDN上使用TMultiReadExclusiveWriteSynchronizer,在"锁定Chris,我即将......"一节中似乎错误地暗示我刚刚提到的场景实际上会死锁.这可能是关于Delphi的早期版本或它可能只是错误.或者我可能误解了它的主张.但是看一下文章的一些评论.)

因此,不再假设有关上下文的任何内容,您显示的代码几乎肯定是不正确的.在读取锁定时检查值,然后将其提升为写入锁定并假设值未更改是错误的.这是TMuliReadExclusiveWriteSynchronizer的一个非常微妙的问题.

以下是Delphi库代码中注释的一些选定部分:

在授予写锁定之前调用BeginWrite时,其他线程有机会修改受保护资源,即使您已经打开了读锁定.最佳策略是不在写锁定中保留有关受保护资源的任何信息(例如计数或大小).获取或释放写锁定后,始终重新获取受保护资源的样本.BeginWrite的函数结果指示当前线程在等待写锁定时另一个线程是否获得写锁定.返回值True表示获取写锁定而没有其他线程的任何干预修改.返回值False表示另一个线程在您等待时获得写锁定,因此应将MREWS对象保护的资源视为已修改.应丢弃受保护资源的任何样本.通常,最好在获得写锁定后始终重新获取受保护资源的样本.BeginWrite和RevisionLevel属性的布尔结果有助于重新获取样本的计算成本高昂或耗时.

这是一些尝试的代码.创建名为Lock的全局TMultiReadExclusiveWriteSynchronizer.创建两个全局布尔值:Bad和GlobalB.然后启动每个线程的一个实例,并从主程序线程监视Bad的值.

type
  TToggleThread = class(TThread)
  protected
    procedure Execute; override;
  end;

  TTestThread = class(TThread)
  protected
    procedure Execute; override;
  end;

{ TToggleThread }

procedure TToggleThread.Execute;
begin
  while not Terminated do
  begin
    Lock.BeginWrite;
    try
      GlobalB := not GlobalB;
    finally
      Lock.EndWrite;
    end;
  end;
end;

{ TTestThread }

procedure TTestThread.Execute;
begin
  while not Terminated do
  begin
    Lock.BeginRead;
    try
      if GlobalB then
      begin
        Lock.BeginWrite;
        try
          if not GlobalB then
          begin
            Bad := True;
            Break;
          end;
        finally
          Lock.EndWrite;
        end;
      end;
    finally
      Lock.EndRead;
    end;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

虽然它是不确定的,但您可能会很快(不到1秒)看到值Bad被设置为True.所以基本上你看到GlobalB的值是True然后当你第二次检查它时它是False,即使在BeginRead/EndRead对之间发生了两次检查(原因是因为里面也有一个BeginWrite/EndWrite对) .

我的个人建议:永远不要将读锁定提升到写锁定.这很容易弄错.在任何情况下,你永远不会真正提升对写锁的读锁(因为你暂时失去了读锁),所以你也可以通过在BeginWrite之前调用EndRead在代码中明确它.是的,这意味着您必须在BeginWrite内再次检查条件.因此,对于您最初显示的代码,我甚至根本不打算使用读锁定.刚开始使用BeginWrite,因为它可能决定写.