将字符串用作锁定对象是否可以?

时间:2012-10-09 17:01:31

标签: c# .net multithreading .net-4.0 locking

我需要在一组有限的字符串的基础上在一个区域中创建一个关键部分。我希望为同一个字符串实例共享锁(有点类似于String.Intern方法)。

我正在考虑以下实施:

public class Foo
{
    private readonly string _s;
    private static readonly HashSet<string> _locks = new HashSet<string>();

    public Foo(string s)
    {
        _s = s;
        _locks.Add(s);
    }

    public void LockMethod()
    {
        lock(_locks.Single(l => l == _s))
        {
            ...
        }
    }
}

这种方法有问题吗?以这种方式锁定字符串对象是否可以,使用HashSet<string>时是否存在任何线程安全问题?

例如,创建一个为每个字符串实例创建新锁定对象的Dictionary<string, object>会更好吗?


最终实施

基于我提出的以下实施建议:

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

    public Foo(string s)
    {
        _s = s;
    }

    public void LockMethod()
    {
        lock(_locks.GetOrAdd(_s, _ => new object()))
        {
            ...
        }
    }
}

4 个答案:

答案 0 :(得分:24)

我个人说,这是一个非常糟糕的主意。这不是字符串的用途。

(我个人不喜欢这样一个事实,即每个对象首先都有一个显示器,但这是一个稍微不同的问题。)

如果你想要一个代表一个可以在不同实例之间共享的锁的对象,为什么不为它创建一个特定的类型呢?您可以很容易地为锁定一个名称以进行诊断,但锁定实际上不是字符串的用途。像这样:

public sealed class Lock
{
    private readonly string name;

    public string Name { get { return name; } }

    public Lock(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }
        this.name = name;
    }
}

鉴于字符串有时实习并且有时没有(以某种方式偶尔难以通过简单检查辨别),你很容易就会结束意外地共享您不想要的锁。

答案 1 :(得分:23)

不鼓励锁定字符串,主要原因是(因为字符串实际),其他一些代码可能会在不知道这一点的情况下锁定同一个字符串实例。创造陷入僵局的可能性。

现在,在大多数具体情况下,这可能是一个牵强附会的场景。这更像是图书馆的一般规则。

但另一方面,字符串的感知益处是什么?

所以,指出要点:

  

这种方法有问题吗?

是的,但主要是理论上的。

  

以这种方式锁定字符串对象是否可以,并且在使用HashSet时是否存在任何线程安全问题?

只要线程只同时读取,HashSet<>就不涉及线程安全。

  

例如,创建一个为每个字符串实例创建新锁定对象的Dictionary会更好吗?

是。只是为了安全起见。在大型系统中,避免死锁的主要目的是使锁定对象尽可能保持本地和私有。只有有限数量的代码才能访问它们。

答案 2 :(得分:6)

锁定字符串可能会有问题,因为实习字符串本质上是全局的。

Interned字符串是每个进程,因此它们甚至可以在不同的AppDomain之间共享。类型对象也是如此(所以不要锁定typeof(x))。

答案 3 :(得分:2)

我不久前有一个类似的问题,我正在寻找一种基于字符串值锁定代码段的好方法。这就是我们现在所拥有的,它解决了实习字符串的问题,并具有我们想要的粒度。

主要思想是使用字符串键维护静态ConcurrentDictionary同步对象。当一个线程进入该方法时,它会立即建立一个锁并尝试将同步对象添加到并发字典中。如果我们可以添加到并发字典,这意味着没有其他线程基于我们的字符串键有锁,我们可以继续我们的工作。否则,我们将使用并发字典中的sync对象来建立第二个锁,它将等待正在运行的线程完成处理。释放第二个锁时,我们可以尝试再次将当前线程的同步对象添加到字典中。

提醒一句:线程没有排队 - 所以如果多个具有相同字符串键的线程同时竞争锁定,则无法保证它们的处理顺序。

如果你认为我忽略了某些事情,请随意批评。

public class Foo
{
    private static ConcurrentDictionary<string, object> _lockDictionary = new ConcurrentDictionary<string, object>();

    public void DoSomethingThreadCriticalByString(string lockString)
    {
        object thisThreadSyncObject = new object();

        lock (thisThreadSyncObject)
        {
            try
            {
                for (; ; )
                {
                   object runningThreadSyncObject = _lockDictionary.GetOrAdd(lockString, thisThreadSyncObject);
                   if (runningThreadSyncObject == thisThreadSyncObject)
                       break;

                    lock (runningThreadSyncObject)
                    {
                        // Wait for the currently processing thread to finish and try inserting into the dictionary again.
                    }
                }

                // Do your work here.

            }
            finally
            {
                // Remove the key from the lock dictionary
                object dummy;
                _lockDictionary.TryRemove(lockString, out dummy);
            }
        }
    }
}