【问题标题】:cleaning up unused locks in a NamedReaderWriterLocker清理 NamedReaderWriterLocker 中未使用的锁
【发布时间】:2017-04-04 21:07:40
【问题描述】:

为了进行异步文件 IO,我创建了一个类,让我可以根据字符串键进行锁定,以防止同时对同一个文件进行多次写入或防止同时进行写入和读取。然而,我面临的问题是可以通过每次发送不同的密钥来增加 _lockDict。未清理旧的未使用锁,但我不知道如何以线程安全的方式执行此操作。这可能会导致非常大的内存消耗。

根据密钥返回锁实例的类:

    public class AsyncNamedReaderWriterLocker
{
    private readonly object _mutex = new object();
    private readonly Dictionary<string, AsyncReaderWriterLock> _lockDict = new Dictionary<string, AsyncReaderWriterLock>();

    public Task<IDisposable> EnterReaderLockAsync(string name)
    {
        var locker = GetLock(name);
        return locker.EnterReaderLockAsync();
    }

    public Task<IDisposable> EnterWriterLockAsync(string name)
    {
        var locker = GetLock(name);
        return locker.EnterWriterLockAsync();
    }

    private AsyncReaderWriterLock GetLock(string name)
    {
        lock (_mutex)
        {
            if (!_lockDict.TryGetValue(name, out AsyncReaderWriterLock locker))
            {
                locker = new AsyncReaderWriterLock();
                _lockDict.Add(name, locker);
            }
            return locker;
        }
    }

还有锁本身(想法来自:https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/):

   public class AsyncReaderWriterLock
{
    private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
    private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
    private readonly WriterLocker _writerLocker;
    private readonly ReaderLocker _readerLocker;
    private readonly object _mutex = new object();
    private int _locksHeld;

    public AsyncReaderWriterLock()
    {
        _writerLocker = new WriterLocker(this);
        _readerLocker = new ReaderLocker(this);
    }

    public Task<IDisposable> EnterReaderLockAsync()
    {
        lock (_mutex)
        {
            if (_locksHeld >= 0 && _writerQueue.Count == 0)
            {
                _locksHeld++;
                return Task.FromResult<IDisposable>(_readerLocker);
            }
            var tcs = new TaskCompletionSource<IDisposable>();
            _readerQueue.Enqueue(tcs);
            return tcs.Task;
        }
    }

    public Task<IDisposable> EnterWriterLockAsync()
    {
        lock (_mutex)
        {
            if (_locksHeld == 0)
            {
                _locksHeld = -1;
                return Task.FromResult<IDisposable>(_writerLocker);
            }
            var tcs = new TaskCompletionSource<IDisposable>();
            _writerQueue.Enqueue(tcs);
            return tcs.Task;
        }
    }

    private void ReleaseLocks()
    {
        if (_locksHeld != 0)
            return;

        // Give priority to writers.
        if (_writerQueue.Count != 0)
        {
            _locksHeld = -1;
            var tcs = _writerQueue.Dequeue();
            tcs.TrySetResult(_writerLocker);
            return;
        }

        // Then to readers.
        while (_readerQueue.Count != 0)
        {
            var tcs = _readerQueue.Dequeue();
            tcs.TrySetResult(_readerLocker);
            ++_locksHeld;
        }
    }

    private void ReleaseReaderLock()
    {
        lock (_mutex)
        {
            _locksHeld--;
            ReleaseLocks();
        }
    }

    private void ReleaseWriterLock()
    {
        lock (_mutex)
        {
            _locksHeld = 0;
            ReleaseLocks();
        }
    }

    private class ReaderLocker : IDisposable
    {
        private readonly AsyncReaderWriterLock _asyncReaderWriterLock;

        internal ReaderLocker(AsyncReaderWriterLock asyncReaderWriterLock)
        {
            _asyncReaderWriterLock = asyncReaderWriterLock;
        }

        public void Dispose()
        {
            _asyncReaderWriterLock.ReleaseReaderLock();
        }
    }

    private class WriterLocker : IDisposable
    {
        private readonly AsyncReaderWriterLock _asyncReaderWriterLock;

        internal WriterLocker(AsyncReaderWriterLock asyncReaderWriterLock)
        {
            _asyncReaderWriterLock = asyncReaderWriterLock;
        }

        public void Dispose()
        {
            _asyncReaderWriterLock.ReleaseWriterLock();
        }
    }
}

使用 AsyncNamedReaderWriterLocker 类的示例:

    public class AsyncFileIO
{
    private static readonly AsyncNamedReaderWriterLocker AsyncNamedReaderWriterLocker = new AsyncNamedReaderWriterLocker();

    public async Task CreateFile(string filename, byte[] data)
    {
        using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
        {
            var directory = Path.GetDirectoryName(filename);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
            using (var stream = File.Create(filename))
            {
                await stream.WriteAsync(data, 0, data.Length);
            }
        }
    }

    public async Task<byte[]> ReadFile(string filename)
    {
        using (await AsyncNamedReaderWriterLocker.EnterReaderLockAsync(filename))
        {
            if (!File.Exists(filename)) return null;
            using (var stream = File.OpenRead(filename))
            {
                var data = new byte[stream.Length];
                await stream.ReadAsync(data, 0, 0);
                return data;
            }
        }
    }

    public async Task DeleteFile(string filename)
    {
        using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
        {
            var directoryName = Path.GetDirectoryName(filename);
            if (!Directory.Exists(directoryName)) return;
            File.Delete(filename);
        }
    }
}

请注意,我最感兴趣的是出于学习目的,并且完全意识到这对于大多数应用程序来说都是多余的。

【问题讨论】:

  • 您需要添加一种方法来将锁标记为未使用。这可能会在任何客户端分发文件名时完成。一旦确定不再需要文件名,它就可以告诉您的AsyncNamedReaderWriterLocker 转储锁。话虽如此,同一个参与者可以很容易地协调谁读取和写入什么文件,这样您就不需要并发文件访问。要获得更具体的信息,准确了解您要完成的工作会有所帮助,以及为什么需要并发文件访问而不是所述的异步访问?
  • 异步处理也带来了并发性。您可以尝试同时读取和写入同一个文件,从而导致并发。这就是为什么需要锁来防止同时写入和读取同一个文件的原因。此代码将在服务器中用于提供文件和一些额外的逻辑,例如生成图像的缩略图。我正在寻找的是一旦不再使用锁就会被清理(以线程方式)。
  • 你将不得不跟踪有多少线程正在持有或等待锁(你的锁似乎已经有这个信息),然后当一个锁被释放时,如果没有其他线程等待,从字典中删除锁。当然,这将需要在释放步骤中进行额外的同步,以确保在释放锁的过程中没有线程从字典中检索锁。我怀疑这是否比只对所有资源使用一个锁更好(特别是考虑到所有 I/O 操作都可能在磁盘上出现瓶颈)。

标签: c# multithreading asynchronous thread-safety locking


【解决方案1】:

起初我在尝试同步异步命名锁以删除未使用的锁时遇到了死锁。这是因为并非所有锁都以相同的顺序释放。解决此问题需要一个专门的 AsyncReaderWriter 锁,仅用于此异步命名锁。现在看起来像这样:

    public class NamedAsyncReaderWriterLockController<TKey>
{
    private readonly object _mutex = new object();
    private readonly Dictionary<TKey, NamedAsyncReaderWriterLock> _lockDict = new Dictionary<TKey, NamedAsyncReaderWriterLock>();


    public Task<IDisposable> EnterReaderLockAsync(TKey name)
    {
        lock (_mutex)
        {
            var locker = GetLock(name);
            return locker.EnterReaderLockAsync();
        }
    }

    public Task<IDisposable> EnterWriterLockAsync(TKey name)
    {
        lock (_mutex)
        {
            var locker = GetLock(name);
            return locker.EnterWriterLockAsync();
        }
    }

    private NamedAsyncReaderWriterLock GetLock(TKey name)
    {
        NamedAsyncReaderWriterLock locker;
        if (!_lockDict.TryGetValue(name, out locker))
        {
            locker = new NamedAsyncReaderWriterLock(this, name, _mutex);
            _lockDict.Add(name, locker);
        }
        return locker;
    }

    private void RemoveLock(TKey name)
    {
        _lockDict.Remove(name);
    }

    private class NamedAsyncReaderWriterLock
    {
        private readonly TKey _name;
        private readonly NamedAsyncReaderWriterLockController<TKey> _namedAsyncReaderWriterLockController;
        private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
        private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
        private readonly NamedWriterLock _namedWriterLock;
        private readonly NamedReaderLock _namedReaderLock;
        private readonly object _mutex = new object();
        private readonly object _releaseMutex;
        private int _locksHeld;

        public NamedAsyncReaderWriterLock(NamedAsyncReaderWriterLockController<TKey> namedAsyncReaderWriterLockController, TKey name, object releaseMutex)
        {
            _namedWriterLock = new NamedWriterLock(this);
            _namedReaderLock = new NamedReaderLock(this);
            _releaseMutex = releaseMutex;
            _name = name;
            _namedAsyncReaderWriterLockController = namedAsyncReaderWriterLockController;
        }

        public Task<IDisposable> EnterReaderLockAsync()
        {
            lock (_mutex)
            {
                if (_locksHeld >= 0 && _writerQueue.Count == 0)
                {
                    _locksHeld++;
                    return Task.FromResult<IDisposable>(_namedReaderLock);
                }
                var tcs = new TaskCompletionSource<IDisposable>();
                _readerQueue.Enqueue(tcs);
                return tcs.Task;
            }
        }

        public Task<IDisposable> EnterWriterLockAsync()
        {
            lock (_mutex)
            {
                if (_locksHeld == 0)
                {
                    _locksHeld = -1;
                    return Task.FromResult<IDisposable>(_namedWriterLock);
                }
                var tcs = new TaskCompletionSource<IDisposable>();
                _writerQueue.Enqueue(tcs);
                return tcs.Task;
            }
        }

        private void ReleaseLocks()
        {
            if (_locksHeld != 0)
                return;

            // Give priority to writers.
            if (_writerQueue.Count != 0)
            {
                _locksHeld = -1;
                var tcs = _writerQueue.Dequeue();
                tcs.TrySetResult(_namedWriterLock);
                return;
            }

            // Then to readers.
            while (_readerQueue.Count != 0)
            {
                var tcs = _readerQueue.Dequeue();
                tcs.TrySetResult(_namedReaderLock);
                ++_locksHeld;
            }

            if (_locksHeld == 0) _namedAsyncReaderWriterLockController.RemoveLock(_name);
        }

        private void ReleaseReaderLock()
        {
            lock (_releaseMutex)
            {
                lock (_mutex)
                {
                    _locksHeld--;
                    ReleaseLocks();
                }
            }
        }

        private void ReleaseWriterLock()
        {
            lock (_releaseMutex)
            {
                lock (_mutex)
                {
                    _locksHeld = 0;
                    ReleaseLocks();
                }
            }
        }

        private class NamedReaderLock : IDisposable
        {
            private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;

            internal NamedReaderLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
            {
                _namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
            }

            public void Dispose()
            {
                _namedAsyncReaderWriterLock.ReleaseReaderLock();
            }
        }

        private class NamedWriterLock : IDisposable
        {
            private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;

            internal NamedWriterLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
            {
                _namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
            }

            public void Dispose()
            {
                _namedAsyncReaderWriterLock.ReleaseWriterLock();
            }
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-29
    • 2013-08-24
    • 1970-01-01
    • 2021-05-22
    • 2016-05-25
    • 1970-01-01
    相关资源
    最近更新 更多