使用string作为锁来进行线程同步

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

private static string mutex= "ABC"; internal static void Foo(Rpc rpc) { lock (mutex) { //do something } } 

这样的字符串(来自代码)可以“ 实习 ”。 这意味着“ABC”的所有实例都指向同一个对象。 即使在AppDomain中,您也可以指向同一个对象(对于提示,请使用Steven)。

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

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

最好使用:

  private static readonly object mutex = new object(); 

此外,由于您的字符串不是constreadonly ,您可以更改它。 所以(理论上)可以锁定你的互斥锁。 将互斥锁更改为另一个引用,然后输入一个临界区,因为该锁使用另一个对象/引用。 例:

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

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

 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 _locks = new ConcurrentDictionary(); 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字符串。 如果我选择采用相同的方法锁定应用程序的另一个区域中的字符串,这一点很重要。

根据环境和开发人员对细节的关注,锁定字符串可能是一个好主意或一个坏主意。

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

 class LockableString { public string _String; public object MyLock; //Provide a lock to the data in. public LockableString() { MyLock = new object(); } } 

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

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