c# - 通过字符串锁定。这安全/理智吗?

标签 c# multithreading locking

我需要通过字符串锁定一段代码。当然,以下代码非常不安全:

lock("http://someurl")
{
    //bla
}

所以我一直在想出一个替代方案。我通常不会在这里发布大量代码,但是当涉及到并发编程时,我对制定自己的同步方案有点担心,所以我提交我的代码来询问这样做是否明智以这种方式或是否有更直接的方法。

public class StringLock
{
    private readonly Dictionary<string, LockObject> keyLocks = new Dictionary<string, LockObject>();
    private readonly object keyLocksLock = new object();

    public void LockOperation(string url, Action action)
    {
        LockObject obj;
        lock (keyLocksLock)
        {
            if (!keyLocks.TryGetValue(url,
                                      out obj))
            {
                keyLocks[url] = obj = new LockObject();
            }
            obj.Withdraw();
        }
        Monitor.Enter(obj);
        try
        {
            action();
        }
        finally
        {
            lock (keyLocksLock)
            {
                if (obj.Return())
                {
                    keyLocks.Remove(url);
                }
                Monitor.Exit(obj);
            }
        }
    }

    private class LockObject
    {
        private int leaseCount;

        public void Withdraw()
        {
            Interlocked.Increment(ref leaseCount);
        }

        public bool Return()
        {
            return Interlocked.Decrement(ref leaseCount) == 0;
        }
    }
}

我会这样使用它:

StringLock.LockOperation("http://someurl",()=>{
    //bla
});

好走,还是崩溃和燃烧?

编辑

为了后代,这是我的工作代码。感谢所有建议:

public class StringLock
{
    private readonly Dictionary<string, LockObject> keyLocks = new Dictionary<string, LockObject>();
    private readonly object keyLocksLock = new object();

    public IDisposable AcquireLock(string key)
    {
        LockObject obj;
        lock (keyLocksLock)
        {
            if (!keyLocks.TryGetValue(key,
                                      out obj))
            {
                keyLocks[key] = obj = new LockObject(key);
            }
            obj.Withdraw();
        }
        Monitor.Enter(obj);
        return new DisposableToken(this,
                                   obj);
    }

    private void ReturnLock(DisposableToken disposableLock)
    {
        var obj = disposableLock.LockObject;
        lock (keyLocksLock)
        {
            if (obj.Return())
            {
                keyLocks.Remove(obj.Key);
            }
            Monitor.Exit(obj);
        }
    }

    private class DisposableToken : IDisposable
    {
        private readonly LockObject lockObject;
        private readonly StringLock stringLock;
        private bool disposed;

        public DisposableToken(StringLock stringLock, LockObject lockObject)
        {
            this.stringLock = stringLock;
            this.lockObject = lockObject;
        }

        public LockObject LockObject
        {
            get
            {
                return lockObject;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~DisposableToken()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (disposing && !disposed)
            {
                stringLock.ReturnLock(this);
                disposed = true;
            }
        }
    }

    private class LockObject
    {
        private readonly string key;
        private int leaseCount;

        public LockObject(string key)
        {
            this.key = key;
        }

        public string Key
        {
            get
            {
                return key;
            }
        }

        public void Withdraw()
        {
            Interlocked.Increment(ref leaseCount);
        }

        public bool Return()
        {
            return Interlocked.Decrement(ref leaseCount) == 0;
        }
    }
}

使用如下:

var stringLock=new StringLock();
//...
using(stringLock.AcquireLock(someKey))
{
    //bla
}

最佳答案

通过任意字符串实例锁定不是一个好主意,因为 Monitor.Lock 会锁定该实例。如果您有两个具有相同内容的不同字符串实例,那将是两个独立的锁,这是您不想要的。所以您对任意字符串的关注是正确的。

但是,.NET 已经有一个内置机制来返回给定字符串内容的“规范实例”:String.Intern .如果您将两个具有相同内容的不同字符串实例传递给它,您将两次返回相同的结果实例。

lock (string.Intern(url)) {
    ...
}

这个比较简单;供您测试的代码更少,因为您将依赖于框架中已有的内容(大概已经可以工作了)。

关于c# - 通过字符串锁定。这安全/理智吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4224888/

相关文章:

java - 您可以将同步成本推到一个线程上吗?

python - 当主线程调用 sys.exit() 时,其他线程会发生什么?

java - 如何暂停/恢复或更改TimerTask的周期时间?

c# - 如何将 MailMessage 对象作为 *.eml 或 *.msg 文件保存到磁盘

c# - EF6 - 在 SaveChanges() 之前截断字符串字段以避免数据库冲突

java - Spring中的异步流程

java - 使用ReentrantLock代替synchronized来提高性能

锁屏后调用Android onCreate

c# - 将日志重定向到logstash实例而不是ElasticSearch

c# - 如何设置datagridview中指定行的背景颜色?