推动read-lock进行写锁时,TMultiReadExclusiveWriteSynchronizer的行为 [英] Behaviour of TMultiReadExclusiveWriteSynchronizer when promoting read-lock to write-lock

查看:504
本文介绍了推动read-lock进行写锁时,TMultiReadExclusiveWriteSynchronizer的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何实现这样的同步结构:

  Lock.BeginRead 
try
if然后更改
begin
Lock.BeginWrite;
try
更新;
finally
Lock.EndWrite;
结束
// ...做一些其他的东西...
end;
finally
Lock.EndRead;
结束

而不会在EndWrite之后丢失读锁定,这样在这个代码块是执行。



在这种情况下,Delphi 2009的TMuliReadExclusiveWriteSynchronizer的行为如何?

解决方案

似乎这个问题包含了两个标准:




  • 而不会在EndWrite之后丢失读取锁

  • 执行代码块时不会执行其他写入程序



我不会再提到第一点,因为其他人已经这样做了。但是第二点非常微妙,需要解释。



首先,让我说我指的是Delphi 2007.我无法访问2009年。但是



您显示的代码使可以让其他作者更改值在代码块期间。当读锁被提升为写锁时,读锁暂时丢失。当你的线程没有读或写锁时,有一个时刻。这是设计的,因为否则死锁几乎可以肯定。 如果促进读写锁的线程在执行此操作时实际上持有读锁定,则可能会很容易发生以下情况:



< (线程1)获取读锁
  • (线程2)获取读锁(好的,读锁是共享的)

  • (线程1)获取写锁定(块;线程2具有读锁定)
  • (线程2)获取写锁(块;线程1有一个读锁 - 现在为了防止这种情况,TMuliReadExclusiveWriteSynchronizer在获取写锁定之前释放一些即时的读锁。



    (附注:文章在EDN上使用TMultiReadExclusiveWriteSynchronizer 在锁定克里斯,我即将...一节中,似乎错误地表明我刚才提到的场景实际上会死锁,这可能是关于一个以前的Delphi版本的,只是被误解或者我可能会误会这是什么声称。但是,请查看文章中的一些意见。)



    所以,不要再对上下文做任何事情了,所显示的代码几乎肯定是错误的。在读取锁定时检查值,然后将其推送到写入锁定,并假定值未更改是错误的。这是一个非常微妙的捕获与TMuliReadExclusiveWriteSynchronizer。



    以下是Delphi库代码中的几个部分注释:


    其他线程有机会修改受保护的资源
    当您在授予写锁定之前调用BeginWrite,即使
    ,如果您已经有一个读锁定。最好的政策是不要保留
    任何有关受保护资源的信息(如计数或大小)跨
    写入锁。在
    获取或释放写锁后,始终重新获取受保护资源的样本。 BeginWrite的函数结果指示当前线程正在等待写入锁定时,另一个线程是否获得
    的写入锁定。
    返回值True表示在没有
    的情况下获取了其他线程进行的任何修改。 False
    的返回值意味着另一个线程在等待时得到了写入锁,因此应该认为修改了MREWS对象所保护的
    资源。
    应该丢弃受保护资源的任何样本。
    一般来说,最好是在获取写锁后总是重新获取受保护的
    资源的样本。 BeginWrite
    和RevisionLevel属性的布尔值结果帮助重新获取样本
    的情况在计算上昂贵或耗时。


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

     键入
    TToggleThread = class(TThread)
    protected
    procedure Execute;覆盖
    结束

    TTestThread = class(TThread)
    protected
    procedure Execute;覆盖
    结束

    {TToggleThread}

    程序TToggleThread.Execute;
    begin
    而不是终止do
    begin
    Lock.BeginWrite;
    try
    GlobalB:= not GlobalB;
    finally
    Lock.EndWrite;
    结束
    结束
    结束

    {TTestThread}

    程序TTestThread.Execute;
    begin
    而不是终止do
    begin
    Lock.BeginRead;
    尝试
    如果GlobalB然后
    开始
    Lock.BeginWrite;
    尝试
    如果不是GlobalB然后
    begin
    Bad:= True;
    休息;
    结束
    finally
    Lock.EndWrite;
    结束
    结束
    finally
    Lock.EndRead;
    结束
    结束
    结束

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



    我的个人建议:永远不要推荐对锁定的读锁定。这太容易弄错了。在任何情况下,你永远不会真正地提出一个读锁定到一个写锁(因为你暂时失去读锁),所以你可以通过在BeginWrite之前调用EndRead在代码中显式。是的,这意味着你必须在BeginWrite里重新检查一下这个条件。所以在你原来显示的代码的情况下,我甚至不会打扰读取锁。只要从BeginWrite开始,因为它可以决定写。


    How can I achieve a synchronization structure like that:

    Lock.BeginRead
    try
      if Changed then
        begin
        Lock.BeginWrite;
        try
          Update;
        finally
          Lock.EndWrite;
        end;
        // ... do some other stuff ...
        end;
    finally
      Lock.EndRead;
    end;
    

    without loosing the read lock after the EndWrite, so that no other writers can execute while this code block is executed.

    How does Delphi 2009's TMuliReadExclusiveWriteSynchronizer behave in this case?

    解决方案

    It seems there are two criteria wrapped up in this question:

    • "without losing the read lock after the EndWrite"
    • "no other writers can execute while this code block is executed"

    I will not address the first point further since others have already done so. However the second point is very delicate and needs explanation.

    First of all, let me say I am referring to Delphi 2007. I do not have access to 2009. However it is unlikely that the behavior I'm describing would have changed.

    The code you shows does make it possible for other writers to change the value during the code block. When the read lock is promoted to a write lock, the read lock is temporarily lost. There is an instant of time when your thread has neither a read or write lock. This is by design, since otherwise deadlock would be almost certain. If the thread which is promoting a read lock to a write lock actually held the read lock while doing so, the following scenario could quite easily occur:

    1. (thread 1) get read lock
    2. (thread 2) get read lock (ok, read lock is shared)
    3. (thread 1) get write lock (blocks; thread 2 has a read lock)
    4. (thread 2) get write lock (blocks; thread 1 has a read lock--now deadlocked)

    To prevent this, TMuliReadExclusiveWriteSynchronizer releases the read lock for some "instant" before obtaining the write lock.

    (Side note: The article Working with TMultiReadExclusiveWriteSynchronizer on EDN, in the section "Lock it up Chris, I'm about to..." seems to incorrectly suggest that the scenario I just mentioned actually would deadlock. This could have been written about a prior version of Delphi or it might just be mistaken. Or I might be misunderstanding what it is claiming. However look at some of the comments on the article.)

    So, not assuming anything more about the context, the code you have shown is almost certainly incorrect. Checking a value while you have a read lock, then promoting it to a write lock and assuming the value has not changed is a mistake. This is a very subtle catch with TMuliReadExclusiveWriteSynchronizer.

    Here are a few selected parts of the comments in the Delphi library code:

    Other threads have an opportunity to modify the protected resource when you call BeginWrite before you are granted the write lock, even if you already have a read lock open. Best policy is not to retain any info about the protected resource (such as count or size) across a write lock. Always reacquire samples of the protected resource after acquiring or releasing a write lock. The function result of BeginWrite indicates whether another thread got the write lock while the current thread was waiting for the write lock. Return value of True means that the write lock was acquired without any intervening modifications by other threads. Return value of False means another thread got the write lock while you were waiting, so the resource protected by the MREWS object should be considered modified. Any samples of the protected resource should be discarded. In general, it's better to just always reacquire samples of the protected resource after obtaining a write lock. The boolean result of BeginWrite and the RevisionLevel property help cases where reacquiring the samples is computationally expensive or time consuming.

    Here is some code to try. Create a global TMultiReadExclusiveWriteSynchronizer named Lock. Create two global Booleans: Bad and GlobalB. Then start one instance of each of these threads and monitor the value of Bad from your main program thread.

    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;
    

    Although it is non-deterministic, you will probably see very quickly (less than 1 second) that the value Bad gets set to True. So basically you see the value of GlobalB is True and then when you check it a second time it is False, even though both checks occurred between a BeginRead/EndRead pair (and the reason is because there was also a BeginWrite/EndWrite pair inside).

    My personal advice: Just never promote a read lock to a write lock. It is way too easy to get it wrong. In any case, you never are really promoting a read lock to a write lock (because you temporarily lose the read lock), so you may as well make it explicit in the code by just calling EndRead before BeginWrite. And yes, that means that you'd have to check the condition again inside the BeginWrite. So in the case of the code you showed originally, I would not even bother with a read lock at all. Just start with BeginWrite because it may decide to write.

    这篇关于推动read-lock进行写锁时,TMultiReadExclusiveWriteSynchronizer的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆