按字符串锁定.这样安全/理智吗?

spe*_*der 28 c# multithreading locking

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

lock("http://someurl")
{
    //bla
}
Run Code Online (Sandbox Code Playgroud)

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我会像这样使用它:

StringLock.LockOperation("http://someurl",()=>{
    //bla
});
Run Code Online (Sandbox Code Playgroud)

很高兴去,或崩溃和燃烧?

编辑

对于后代,这是我的工作代码.感谢所有的建议:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用如下:

var stringLock=new StringLock();
//...
using(stringLock.AcquireLock(someKey))
{
    //bla
}
Run Code Online (Sandbox Code Playgroud)

Joe*_*ite 14

锁定任意字符串实例将是一个坏主意,因为Monitor.Lock锁定实例.如果您有两个具有相同内容的不同字符串实例,那么这将是两个您不想要的独立锁.所以你关心任意字符串是正确的.

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

lock (string.Intern(url)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

这更简单; 您可以使用较少的代码进行测试,因为您将依赖于框架中已有的内容(可能已经有效).

  • 并且内部泄漏记忆 (3认同)
  • 除了[String.Intern()缓慢](http://stackoverflow.com/a/19375402/709537)之外,锁定内部字符串会导致性能问题和死锁,[甚至是跨AppDomains](http:// stackoverflow) .com/a/19375402/709537),即应该运行的应用程序与您的应用程序分开. (3认同)

Ian*_*ose 10

另一种选择是获取每个URL的HashCode,然后将其除以素数并将其用作锁数组的索引.这将限制您需要的锁定数量,同时通过选择要使用的锁定数量来控制"错误锁定"的可能性.

但是,如果成本太高,每个活动网址只有一个锁,那么上述情况是值得的.

  • 该代码的最大优点是它实现起来非常简单,因为与基于字典的代码不同,它不需要删除锁定对象来防止内存泄漏.这是本主题中唯一的代码,我有信心在没有太多考虑的情况下正确实现.当然,在一个线程中一次持有多个锁会变得非常难看. (2认同)

Zai*_*sud 5

这是一个非常简单,优雅和正确的.NET 4解决方案,使用ConcurrentDictionary改编自这个问题.

public static class StringLocker
{
    private static readonly ConcurrentDictionary<string, object> _locks = new ConcurrentDictionary<string, object>();

    public static void DoAction(string s, Action action)
    {
        lock(_locks.GetOrAdd(s, new object()))
        {
            action();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以这样使用:

StringLocker.DoAction("http://someurl", () =>
{
    ...
});
Run Code Online (Sandbox Code Playgroud)

  • 此实现永远不会回收未使用的锁,并且最终会在长时间运行的过程中消耗大量内存. (3认同)