如何简化 ReaderWriterLock 的进入和退出操作?

11

对我来说,这听起来非常嘈杂。五行过多的开销实在太大了。

m_Lock.EnterReadLock()
Try
    Return m_List.Count
Finally
    m_Lock.ExitReadLock()
End Try

那么你如何简化这个呢?

5个回答

23

我也是这么想的,但用C#实现;-p

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ReaderWriterLockSlim sync = new ReaderWriterLockSlim();

        using (sync.Read())
        {
           // etc    
        }
    }


}
public static class ReaderWriterExt
{
    sealed class ReadLockToken : IDisposable
    {
        private ReaderWriterLockSlim sync;
        public ReadLockToken(ReaderWriterLockSlim sync)
        {
            this.sync = sync;
            sync.EnterReadLock();
        }
        public void Dispose()
        {
            if (sync != null)
            {
                sync.ExitReadLock();
                sync = null;
            }
        }
    }
    public static IDisposable Read(this ReaderWriterLockSlim obj)
    {
        return new ReadLockToken(obj);
    }
}

1
有趣的是,我早已忘记了这件事,但恰巧它出现在我的声望点数中,正好在我需要它的时候。 - Jonathan Allen
写作方面怎么样? - kofifus
@kofifus EnterWriteLock也是同样的事情吗? - Marc Gravell
@MarcGravell,这里不使用结构体的原因是什么? - Yair Halberstadt
@YairHalberstadt 可变性位是一个变化;个人而言,我会在这里使用一个结构体,可能是只读结构体,如果有人调用了两次dispose:那就是他们错误使用的问题,但是...这是一个变化。 - Marc Gravell

6
到目前为止发布的所有解决方案都存在死锁风险。 像这样使用using块:
ReaderWriterLockSlim sync = new ReaderWriterLockSlim();
using (sync.Read())
{
  // Do stuff
}

被转换成类似于这样的内容:

ReaderWriterLockSlim sync = new ReaderWriterLockSlim();
IDisposable d = sync.Read();
try
{
  // Do stuff
}
finally
{
  d.Dispose();
}

这意味着在sync.Read()和try块之间可能会发生ThreadAbortException(或类似异常)。当这种情况发生时,finally块永远不会被调用,锁也永远不会被释放!更多信息和更好的实现请参见:使用ReaderWriterLockSlim和其他锁对象的死锁。简而言之,更好的实现方法是将锁移到try块中,如下所示:
ReaderWriterLockSlim myLock = new ReaderWriterLockSlim();
try
{
    myLock.EnterReadLock();
    // Do stuff
}
finally
{
    // Release the lock
    myLock.ExitReadLock();
}

一个类似于被接受答案中的包装器的实现如下:

  /// <summary>
  /// Manager for a lock object that acquires and releases the lock in a manner
  /// that avoids the common problem of deadlock within the using block
  /// initialisation.
  /// </summary>
  /// <remarks>
  /// This manager object is, by design, not itself thread-safe.
  /// </remarks>
  public sealed class ReaderWriterLockMgr : IDisposable
  {
    /// <summary>
    /// Local reference to the lock object managed
    /// </summary>
    private ReaderWriterLockSlim _readerWriterLock = null;

    private enum LockTypes { None, Read, Write, Upgradeable }
    /// <summary>
    /// The type of lock acquired by this manager
    /// </summary>
    private LockTypes _enteredLockType = LockTypes.None;

    /// <summary>
    /// Manager object construction that does not acquire any lock
    /// </summary>
    /// <param name="ReaderWriterLock">The lock object to manage</param>
    public ReaderWriterLockMgr(ReaderWriterLockSlim ReaderWriterLock)
    {
      if (ReaderWriterLock == null)
        throw new ArgumentNullException("ReaderWriterLock");
      _readerWriterLock = ReaderWriterLock;
    }

    /// <summary>
    /// Call EnterReadLock on the managed lock
    /// </summary>
    public void EnterReadLock()
    {
      if (_readerWriterLock == null)
        throw new ObjectDisposedException(GetType().FullName);
      if (_enteredLockType != LockTypes.None)
        throw new InvalidOperationException("Create a new ReaderWriterLockMgr for each state you wish to enter");
      // Allow exceptions by the Enter* call to propogate
      // and prevent updating of _enteredLockType
      _readerWriterLock.EnterReadLock();
      _enteredLockType = LockTypes.Read;
    }

    /// <summary>
    /// Call EnterWriteLock on the managed lock
    /// </summary>
    public void EnterWriteLock()
    {
      if (_readerWriterLock == null)
        throw new ObjectDisposedException(GetType().FullName);
      if (_enteredLockType != LockTypes.None)
        throw new InvalidOperationException("Create a new ReaderWriterLockMgr for each state you wish to enter");
      // Allow exceptions by the Enter* call to propogate
      // and prevent updating of _enteredLockType
      _readerWriterLock.EnterWriteLock();
      _enteredLockType = LockTypes.Write;
    }

    /// <summary>
    /// Call EnterUpgradeableReadLock on the managed lock
    /// </summary>
    public void EnterUpgradeableReadLock()
    {
      if (_readerWriterLock == null)
        throw new ObjectDisposedException(GetType().FullName);
      if (_enteredLockType != LockTypes.None)
        throw new InvalidOperationException("Create a new ReaderWriterLockMgr for each state you wish to enter");
      // Allow exceptions by the Enter* call to propogate
      // and prevent updating of _enteredLockType
      _readerWriterLock.EnterUpgradeableReadLock();
      _enteredLockType = LockTypes.Upgradeable;
    }

    /// <summary>
    /// Exit the lock, allowing re-entry later on whilst this manager is in scope
    /// </summary>
    /// <returns>Whether the lock was previously held</returns>
    public bool ExitLock()
    {
      switch (_enteredLockType)
      {
        case LockTypes.Read:
          _readerWriterLock.ExitReadLock();
          _enteredLockType = LockTypes.None;
          return true;
        case LockTypes.Write:
          _readerWriterLock.ExitWriteLock();
          _enteredLockType = LockTypes.None;
          return true;
        case LockTypes.Upgradeable:
          _readerWriterLock.ExitUpgradeableReadLock();
          _enteredLockType = LockTypes.None;
          return true;
      }
      return false;
    }

    /// <summary>
    /// Dispose of the lock manager, releasing any lock held
    /// </summary>
    public void Dispose()
    {
      if (_readerWriterLock != null)
      {
        ExitLock();
        // Tidy up managed resources
        // Release reference to the lock so that it gets garbage collected
        // when there are no more references to it
        _readerWriterLock = null;
        // Call GC.SupressFinalize to take this object off the finalization
        // queue and prevent finalization code for this object from
        // executing a second time.
        GC.SuppressFinalize(this);
      }
    }

    protected ~ReaderWriterLockMgr()
    {
      if (_readerWriterLock != null)
        ExitLock();
      // Leave references to managed resources so that the garbage collector can follow them
    }
  }

使用方法如下:
ReaderWriterLockSlim myLock = new ReaderWriterLockSlim();

using (ReaderWriterLockMgr lockMgr = new ReaderWriterLockMgr(myLock))
{
    lockMgr.EnterReadLock();
    // Do stuff
}

此外,根据Joe Duffy的博客,该锁不具备异步异常(例如线程中止和内存不足)的强大鲁棒性。如果在锁定方法执行期间发生其中之一,锁状态可能会损坏,导致后续死锁、未处理异常以及(遗憾的是)由于内部使用自旋锁,CPU利用率达到100%。因此,如果您要在经常使用线程中止或尝试恢复硬件OOM的环境中运行代码,则不会对此锁感到满意。

如果有人频繁抛出ThreadAbortExceptions,那么就不仅仅是死锁问题了。唯一适当的情况是线程本身引发ThreadAbortException,例如在调用HttpResponse.End时。 - Jonathan Allen
我认为这是一个很好的观点,应该得到更多的关注。在阅读到这个之前,我真的很喜欢Marc Gravell的回答。 - Pandincus
3
两个链接都失效了。 - Beachwalker
“...而且锁永远不会被释放!”但是垃圾回收器难道不会在稍后的时间内调用它,因为d不再被引用吗?所以在大多数情况下,“问题”并不那么重要。只是一个猜测... - Beachwalker
@Beachwalker GC不会调用Dispose,但你可以在类中有一个析构函数来调用Dispose:https://dev59.com/questions/TnI-5IYBdhLWcg3wwLW3 - Alex
好的,我的陈述没有表达得够清楚。我假设这个类在析构函数中有一个调用,并且已经正确实现了。因此,垃圾回收器会隐式地调用它。 - Beachwalker

2

这不是我的发明,但它确实让我的头发变得少了一些灰色。

internal static class ReaderWriteLockExtensions
{
    private struct Disposable : IDisposable
    {
        private readonly Action m_action;
        private Sentinel m_sentinel;

        public Disposable(Action action)
        {
            m_action = action;
            m_sentinel = new Sentinel();
        }

        public void Dispose()
        {
            m_action();
            GC.SuppressFinalize(m_sentinel);
        }
    }

    private class Sentinel
    {
        ~Sentinel()
        {
            throw new InvalidOperationException("Lock not properly disposed.");
        }
    }

    public static IDisposable AcquireReadLock(this ReaderWriterLockSlim lock)
    {
        lock.EnterReadLock();
        return new Disposable(lock.ExitReadLock);
    }

    public static IDisposable AcquireUpgradableReadLock(this ReaderWriterLockSlim lock)
    {
        lock.EnterUpgradeableReadLock();
        return new Disposable(lock.ExitUpgradeableReadLock);
    }

    public static IDisposable AcquireWriteLock(this ReaderWriterLockSlim lock)
    {
        lock.EnterWriteLock();
        return new Disposable(lock.ExitWriteLock);
    }
} 

如何使用:

using (m_lock.AcquireReadLock())
{
    // Do stuff
}

从终结器中抛出异常会导致CLR快速失败,从而拆除进程。因此,应始终避免在终结器中抛出异常。 - Wouter
嗯,在这种情况下,它显示出更大的设计缺陷,因此快速失败是首选。 - Patrik Svensson

0

我最终做了这个,但我仍然对更好的方法或我的设计中的缺陷持开放态度。

Using m_Lock.ReadSection
    Return m_List.Count
End Using

这个使用了这个扩展方法/类:

<Extension()> Public Function ReadSection(ByVal lock As ReaderWriterLockSlim) As ReadWrapper
    Return New ReadWrapper(lock)
End Function


Public NotInheritable Class ReadWrapper
    Implements IDisposable

    Private m_Lock As ReaderWriterLockSlim
    Public Sub New(ByVal lock As ReaderWriterLockSlim)
        m_Lock = lock
        m_Lock.EnterReadLock()
    End Sub
    Public Sub Dispose() Implements IDisposable.Dispose
        m_Lock.ExitReadLock()
    End Sub

End Class

1
两个想法:第一,你应该清除m_Lock,这样双重Dispose()就不会引起问题(不太可能,但...) 第二 - 如果IDisposable足够的话,调用者不需要知道ReadWrapper。但我喜欢它;-p - Marc Gravell
好的,我本来也不想暴露ReadWrapper类型。 - Jonathan Allen

0

由于锁的作用是保护某些内存,因此我认为将该内存封装在“Locked”对象中,并仅通过各种锁令牌(如Mark所提到的)访问它将非常有用:

// Stores a private List<T>, only accessible through lock tokens
//  returned by Read, Write, and UpgradableRead.
var lockedList = new LockedList<T>( );

using( var r = lockedList.Read( ) ) {
  foreach( T item in r.Reader )
    ...
}

using( var w = lockedList.Write( ) ) {
  w.Writer.Add( new T( ) );
}

T t = ...;
using( var u = lockedList.UpgradableRead( ) ) {
  if( !u.Reader.Contains( t ) )
    using( var w = u.Upgrade( ) )
      w.Writer.Add( t );
}

现在访问内部列表的唯一方法是通过调用适当的访问器。

这对于 List<T> 特别有效,因为它已经有了 ReadOnlyCollection<T> 包装器。对于其他类型,您可以始终创建一个 Locked<T,T>,但这样您就会失去良好的可读/可写类型区分。

一个改进的方法可能是将 RW 类型定义为可自行处理的包装器,这将保护免受(无意中的)错误的影响,例如:

List<T> list;
using( var w = lockedList.Write( ) )
  list = w.Writable;

//BAD: "locked" object leaked outside of lock scope
list.MakeChangesWithoutHoldingLock( );

然而,这样做会使Locked使用起来更加复杂,而当前版本在手动锁定共享成员时给您提供了同样的保护。
sealed class LockedList<T> : Locked<List<T>, ReadOnlyCollection<T>> {
  public LockedList( )
    : base( new List<T>( ), list => list.AsReadOnly( ) )
  { }
}

public class Locked<W, R> where W : class where R : class {
  private readonly LockerState state_;
  public Locked( W writer, R reader ) { this.state_ = new LockerState( reader, writer ); }
  public Locked( W writer, Func<W, R> getReader ) : this( writer, getReader( writer ) ) { }

  public IReadable Read( ) { return new Readable( this.state_ ); }
  public IWritable Write( ) { return new Writable( this.state_ ); }
  public IUpgradable UpgradableRead( ) { return new Upgradable( this.state_ ); }


  public interface IReadable : IDisposable { R Reader { get; } }
  public interface IWritable : IDisposable { W Writer { get; } }
  public interface IUpgradable : IReadable { IWritable Upgrade( );}


  #region Private Implementation Details
  sealed class LockerState {
    public readonly R Reader;
    public readonly W Writer;
    public readonly ReaderWriterLockSlim Sync;

    public LockerState( R reader, W writer ) {
      Debug.Assert( reader != null && writer != null );
      this.Reader = reader;
      this.Writer = writer;
      this.Sync = new ReaderWriterLockSlim( );
    }
  }

  abstract class Accessor : IDisposable {
    private LockerState state_;
    protected LockerState State { get { return this.state_; } }
    protected Accessor( LockerState state ) {
      Debug.Assert( state != null );
      this.Acquire( state.Sync );
      this.state_ = state;
    }

    protected abstract void Acquire( ReaderWriterLockSlim sync );
    protected abstract void Release( ReaderWriterLockSlim sync );

    public void Dispose( ) {
      if( this.state_ != null ) {
        var sync = this.state_.Sync;
        this.state_ = null;
        this.Release( sync );
      }
    }
  }

  class Readable : Accessor, IReadable {
    public Readable( LockerState state ) : base( state ) { }
    public R Reader { get { return this.State.Reader; } }
    protected override void Acquire( ReaderWriterLockSlim sync ) { sync.EnterReadLock( ); }
    protected override void Release( ReaderWriterLockSlim sync ) { sync.ExitReadLock( ); }
  }

  sealed class Writable : Accessor, IWritable {
    public Writable( LockerState state ) : base( state ) { }
    public W Writer { get { return this.State.Writer; } }
    protected override void Acquire( ReaderWriterLockSlim sync ) { sync.EnterWriteLock( ); }
    protected override void Release( ReaderWriterLockSlim sync ) { sync.ExitWriteLock( ); }
  }

  sealed class Upgradable : Readable, IUpgradable {
    public Upgradable( LockerState state ) : base( state ) { }
    public IWritable Upgrade( ) { return new Writable( this.State ); }
    protected override void Acquire( ReaderWriterLockSlim sync ) { sync.EnterUpgradeableReadLock( ); }
    protected override void Release( ReaderWriterLockSlim sync ) { sync.ExitUpgradeableReadLock( ); }
  }
  #endregion
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接