使用字符串作为锁来做线程同步

发布于 2024-10-03 09:42:12 字数 245 浏览 3 评论 0原文

当我查看一些遗留应用程序代码时,我注意到它正在使用字符串对象来进行线程同步。我正在尝试解决该程序中的一些线程争用问题,并且想知道这是否会导致一些奇怪的情况。有什么想法吗?

private static string mutex= "ABC";

internal static void Foo(Rpc rpc)
{
    lock (mutex)
    {
        //do something
    }
}

While i was looking at some legacy application code i noticed it is using a string object to do thread synchronization. I'm trying to resolve some thread contention issues in this program and was wondering if this could lead so some strange situations. Any thoughts ?

private static string mutex= "ABC";

internal static void Foo(Rpc rpc)
{
    lock (mutex)
    {
        //do something
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

泪意 2024-10-10 09:42:12

像这样的字符串(来自代码)可能是“interned”。这意味着“ABC”的所有实例都指向同一个对象。即使跨 AppDomain,您也可以指向同一个对象(谢谢史蒂文的提示)。

如果您有很多来自不同位置但具有相同文本的字符串互斥锁,那么它们都可以锁定同一个对象。

实习池保存字符串存储。如果将文字字符串常量分配给多个变量,则每个变量将被设置为引用内部池中的相同常量,而不是引用具有相同值的 String 的多个不同实例。

最好使用:

 private static readonly object mutex = new object();

此外,由于您的字符串不是 const 或 readonly,因此您可以更改它。因此(理论上)可以锁定您的互斥体。将互斥锁更改为另一个引用,然后进入临界区,因为锁使用了另一个对象/引用。例子:

private static string mutex = "1";
private static string mutex2 = "1";  // for 'lock' mutex2 and mutex are the same

private static void CriticalButFlawedMethod() {
    lock(mutex) {
      mutex += "."; // Hey, now mutex points to another reference/object
      // You are free to re-enter
      ...
    }
}

Strings like that (from the code) could be "interned". This means all instances of "ABC" point to the same object. Even across AppDomains you can point to the same object (thx Steven for the tip).

If you have a lot of string-mutexes, from different locations, but with the same text, they could all lock on the same object.

The intern pool conserves string storage. If you assign a literal string constant to several variables, each variable is set to reference the same constant in the intern pool instead of referencing several different instances of String that have identical values.

It's better to use:

 private static readonly object mutex = new object();

Also, since your string is not const or readonly, you can change it. So (in theory) it is possible to lock on your mutex. Change mutex to another reference, and then enter a critical section because the lock uses another object/reference. Example:

private static string mutex = "1";
private static string mutex2 = "1";  // for 'lock' mutex2 and mutex are the same

private static void CriticalButFlawedMethod() {
    lock(mutex) {
      mutex += "."; // Hey, now mutex points to another reference/object
      // You are free to re-enter
      ...
    }
}
嗳卜坏 2024-10-10 09:42:12

为了回答您的问题(正如其他一些人已经提出的那样),您提供的代码示例存在一些潜在的问题:

private static string mutex= "ABC";
  • 变量 mutex 不是不可变的。
  • 字符串文字 "ABC" 将在应用程序中的各处引用相同的内部对象引用。

一般来说,我建议不要锁定字符串。然而,我遇到过一个案例,这样做很有用。

有时我会维护一个锁对象字典,其中的键是我拥有的某些数据的唯一值。这是一个人为的示例:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

ConcurrentDictionary<int, object> _locks = new ConcurrentDictionary<int, object>();    
void DoSomething(SomeEntity entity)
{   
    var mutex = _locks.GetOrAdd(entity.Id, id => new object());

    lock(mutex)
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}   

这样的代码的目标是在实体的 Id 上下文中序列化 DoSomething() 的并发调用。缺点是字典。实体越多,它就越大。这也只是需要阅读和思考更多的代码。

我认为 .NET 的字符串驻留可以简化事情:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

void DoSomething(SomeEntity entity)
{   
    lock(string.Intern("dee9e550-50b5-41ae-af70-f03797ff2a5d:" + entity.Id))
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}

这里的区别是我依靠字符串驻留为每个实体 id 提供相同的对象引用。这简化了我的代码,因为我不必维护互斥体实例的字典。

请注意我用作命名空间的硬编码 UUID 字符串。如果我选择在应用程序的另一个区域采用相同的锁定字符串的方法,这一点很重要。

锁定字符串可能是一个好主意,也可能是一个坏主意,具体取决于具体情况以及开发人员对细节的关注程度。

To answer your question (as some others already have), there are some potential problems with the code example you provided:

private static string mutex= "ABC";
  • The variable mutex is not immutable.
  • The string literal "ABC" will refer to the same interned object reference everywhere in your application.

In general, I would advise against locking on strings. However, there is a case I've ran into where it is useful to do this.

There have been occasions where I have maintained a dictionary of lock objects where the key is something unique about some data that I have. Here's a contrived example:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

ConcurrentDictionary<int, object> _locks = new ConcurrentDictionary<int, object>();    
void DoSomething(SomeEntity entity)
{   
    var mutex = _locks.GetOrAdd(entity.Id, id => new object());

    lock(mutex)
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}   

The goal of code like this is to serialize concurrent invocations of DoSomething() within the context of the entity's Id. The downside is the dictionary. The more entities there are, the larger it gets. It's also just more code to read and think about.

I think .NET's string interning can simplify things:

void Main()
{
    var a = new SomeEntity{ Id = 1 };
    var b = new SomeEntity{ Id = 2 };

    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(a));    
    Task.Run(() => DoSomething(b));    
    Task.Run(() => DoSomething(b));
}

void DoSomething(SomeEntity entity)
{   
    lock(string.Intern("dee9e550-50b5-41ae-af70-f03797ff2a5d:" + entity.Id))
    {
        Console.WriteLine("Inside {0}", entity.Id);
        // do some work
    }
}

The difference here is that I am relying on the string interning to give me the same object reference per entity id. This simplifies my code because I don't have to maintain the dictionary of mutex instances.

Notice the hard-coded UUID string that I'm using as a namespace. This is important if I choose to adopt the same approach of locking on strings in another area of my application.

Locking on strings can be a good idea or a bad idea depending on the circumstances and the attention that the developer gives to the details.

素衣风尘叹 2024-10-10 09:42:12

我的 2 美分:

  1. ConcurrentDictionary 比实习字符串快 1.5 倍。我做了一次基准测试。

  2. 要解决“不断增长的字典”问题,您可以使用信号量字典而不是对象字典。 AKA 使用 ConcurrentDictionary 而不是 。与lock语句不同,信号量可以跟踪有多少线程锁定了它们。一旦所有的锁被释放 - 您可以将其从字典中删除。请参阅此问题以获取类似的解决方案:基于密钥的异步锁定

  3. 信号量甚至更好,因为您甚至可以控制并发级别。就像,您可以“限制为 5 个并发运行”,而不是“限制为 1 个并发运行”。很棒的免费奖金不是吗?我必须编写一个电子邮件服务,该服务需要限制与服务器的并发连接数 - 这非常方便。

My 2 cents:

  1. ConcurrentDictionary is 1.5X faster than interned strings. I did a benchmark once.

  2. To solve the "ever-growing dictionary" problem you can use a dictionary of semaphores instead of a dictionary of objects. AKA use ConcurrentDictionary<string, SemaphoreSlim> instead of <string, object>. Unlike the lock statements, Semaphores can track how many threads have locked on them. And once all the locks are released - you can remove it from the dictionary. See this question for solutions like that: Asynchronous locking based on a key

  3. Semaphores are even better because you can even control the concurrency level. Like, instead of "limiting to one concurrent run" - you can "limit to 5 concurrent runs". Awesome free bonus isn't it? I had to code an email-service that needed to limit the number of concurrent connections to a server - this came very very handy.

爱的那么颓废 2024-10-10 09:42:12

如果需要锁定字符串,您可以创建一个对象,将字符串与可锁定的对象配对。

class LockableString
{
     public string _String; 
     public object MyLock;  //Provide a lock to the data in.

     public LockableString()
     {
          MyLock = new object();
     }
}

If you need to lock a string, you can create an object that pairs the string with an object that you can lock with.

class LockableString
{
     public string _String; 
     public object MyLock;  //Provide a lock to the data in.

     public LockableString()
     {
          MyLock = new object();
     }
}
心碎无痕… 2024-10-10 09:42:12

我想如果生成的字符串很多并且都是唯一的,那么锁定驻留字符串可能会导致内存膨胀。另一种应该提高内存效率并解决直接死锁问题的方法是

// Returns an Object to Lock with based on a string Value
private static readonly ConditionalWeakTable<string, object> _weakTable = new ConditionalWeakTable<string, object>();
public static object GetLock(string value)
{
    if (value == null) throw new ArgumentNullException(nameof(value));
    return _weakTable.GetOrCreateValue(value.ToLower());
}

I imagine that locking on interned strings could lead to memory bloat if the strings generated are many and are all unique. Another approach that should be more memory efficient and solve the immediate deadlock issue is

// Returns an Object to Lock with based on a string Value
private static readonly ConditionalWeakTable<string, object> _weakTable = new ConditionalWeakTable<string, object>();
public static object GetLock(string value)
{
    if (value == null) throw new ArgumentNullException(nameof(value));
    return _weakTable.GetOrCreateValue(value.ToLower());
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文