Using string as a lock to do thread synchronization
Asked Answered
K

5

37

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
    }
}
Kafka answered 16/11, 2010 at 10:5 Comment(0)
M
43

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
      ...
    }
}
Moor answered 16/11, 2010 at 10:10 Comment(6)
Thanks GvS. But I was wondering if there is a difference in using a object type ( which we generally do) to lock against using a string type ( string being mutable ). the program is having large number of thread contentions ( not sure as yet if that is due to this piece of code) .Kafka
Strings are not mutable, they just look mutable, but every different string points to another reference. It looks strange to me to lock on them. Did you try a ReaderWriterLock.Moor
sorry i meant to say "string being immutable" :) ..i didnt try with ReaderWriterLock.. was just trying to figure out the exact section of code that causes the issues. it is hard to recreate the situcation in the local enviornment due insufficient data. but i'm going to spend some trying in re-producing this. thanks again.Kafka
The string is "immutable" but the reference is not. I added some code to illustrate this.Moor
I like to add that .NET is even allowed to share string instances (as well as Type instances) across AppDomains, which could make to AppDomains deadlock on each other! In other words, locking on strings is really scary.Quartern
@Quartern - do you have a reference for the above comment on sharing string instances across AppDomains?Saltant
C
41

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.

Convoy answered 26/4, 2015 at 13:52 Comment(7)
This answer was actually what I was looking for. I need a guarantee that the same string will make the same mutex object and a string interning is a very neat mechanizm to use for that. I need it for purposes of caching: multiple threads may retreive the same object from DB and they must insert only 1 entry to THE cache, which has a key of "Id". So now I just construct a string for locking from this Id and some arbitrary string that will never be used anywhere ever (something like Guid for those who want to be relatively safe), and do the object instantiation and cache update in this lock.Dannie
I'm glad you found this approach useful!Convoy
The problem with the infinitely growinh dictionary can be fixed by using ConditionalWeakTable. This is a dictionary that allows both the keys as the values to go out of scope, which allows things to get garbage collectedmQuartern
@Quartern Agreed about ConditionalWeakTable. That's a really handy class. I've talked about another type of usage in another answer https://mcmap.net/q/128153/-memory-address-of-an-object-in-c I've been using it a lot lately with some object extension methods for attaching anything to anything. It's pretty great.Convoy
it's worth to mention the problem with using string.Intern is that it's also infinitely growing. as stated by MSDN the memory allocated for interned String objects is not likely be released until the common language runtime (CLR) terminates.. it's a sort of a memory leak you are introducing into your code, so it must be used with extra careEpochal
@Epochal agreed. Use with care.Convoy
@Quartern ConditionalWeakTable<string, object> won't work with dynamic strings. It will produce new objects for same string valuesYucca
Y
2

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.

Yucca answered 8/2, 2021 at 8:12 Comment(0)
M
1

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();
     }
}
Marsha answered 30/9, 2013 at 19:35 Comment(0)
S
0

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());
}
Sava answered 16/10, 2018 at 15:39 Comment(1)
This does not work for dynamic string, since a string is not interned then. It produces new lock objects for the same string. GetOrCreateValue make a ReferenceEquals, so same problem exists while using a lock on string. Does not help :(Candlepin

© 2022 - 2024 — McMap. All rights reserved.