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

我需要根据一组有限的字符串在一个区域中做一个关键部分。 我希望为同一字符串实例共享该锁(有点类似于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))
        {
            ...
        }
    }
}

这种方法有什么问题吗? 以这种方式锁定字符串对象是否可以,并且在使用Dictionary<string, object>时是否存在线程安全问题?

例如,创建一个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()))
        {
            ...
        }
    }
}
Zaid Masud asked 2020-08-10T11:19:02Z
4个解决方案
24 votes

不鼓励锁定字符串,主要原因是(由于字符串绑定)某些其他代码可能会在您不知道的情况下锁定相同的字符串实例。 可能导致死锁情况。

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

但是另一方面,字符串的好处是什么呢?

因此,一点一点:

这种方法有什么问题吗?

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

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

只要线程仅同时读取,HashSet<>便不参与线程安全。

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

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

Henk Holterman answered 2020-08-10T11:19:48Z
24 votes

我个人说这是一个非常糟糕的主意。 那不是字符串的目的。

(我个人不喜欢每个对象最初都有一个监视器的事实,但这是一个稍微不同的问题。)

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

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;
    }
}

考虑到字符串有时被嵌入而有时不被嵌入的方式(这种方式有时很难通过简单的检查来辨别),您很容易在不希望使用它们的地方意外地获得了共享锁。

Jon Skeet answered 2020-08-10T11:20:22Z
6 votes

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

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

Brian Rasmussen answered 2020-08-10T11:20:47Z
2 votes

不久前,我遇到了类似的问题,当时我正在寻找一种基于字符串值锁定一段代码的好方法。 这是我们目前可以解决的问题,它可以解决字符串居留的问题,并具有所需的粒度。

主要思想是使用字符串键维护同步对象的静态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);
            }
        }
    }
}
Bill Ravdin answered 2020-08-10T11:21:21Z
translate from https://stackoverflow.com:/questions/12804879/is-it-ok-to-use-a-string-as-a-lock-object