代码之家  ›  专栏  ›  技术社区  ›  Illuminati

使用字符串作为锁进行线程同步

  •  22
  • Illuminati  · 技术社区  · 14 年前

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

    private static string mutex= "ABC";
    
    internal static void Foo(Rpc rpc)
    {
        lock (mutex)
        {
            //do something
        }
    }
    
    4 回复  |  直到 6 年前
        1
  •  36
  •   GvS    14 年前

    这样的字符串(从代码中)可以是“ interned “。这意味着“abc”的所有实例都指向同一对象。甚至跨越 AppDomain 你可以指向同一个物体(尖端是thx steven)。

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

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

    最好使用:

     private static readonly object mutex = new object();
    

    另外,因为你的字符串不是 const readonly ,你可以改变它。所以(理论上)可以锁定互斥体。将mutex更改为另一个引用,然后输入一个关键部分,因为锁使用了另一个对象/引用。例子:

    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
          ...
        }
    }
    
        2
  •  24
  •   Ronnie Overby    10 年前

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

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

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

    我认为.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字符串是命名空间。如果我选择在应用程序的另一个区域中采用同样的方法锁定字符串,这一点很重要。

    锁定字符串可能是一个好主意,也可能是一个坏主意,这取决于环境和开发人员对细节的关注程度。

        3
  •  1
  •   Harry Glinos    11 年前

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

    class LockableString
    {
         public string _String; 
         public object MyLock;  //Provide a lock to the data in.
    
         public LockableString()
         {
              MyLock = new object();
         }
    }
    
        4
  •  0
  •   Ody    6 年前

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

    // 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());
    }