0

I faced with problem on lock statement that confused me:

If concatenate two string with same expression("1" + "2") like below, lock statement realize this expression as a string and lock work as expected:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        lock ("1" + "2")
        {//launched afetr 10 second
                        
        }
    });
    Thread.Sleep(10000);
}

But if change in first lock ("1" + "2") with var a="1"; lock (a + "2")

although two expression has same result but lock statement handle with this as two different expression so second lock statement launched immediately:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched immediately
                        
        }
    });
    Thread.Sleep(10000);
}

Can please explain this behavior:

(I know using string in lock statement (MSDN) violate lock guideline.)

Community
  • 1
  • 1
Reza ArabQaeni
  • 4,848
  • 27
  • 46
  • 4
    The strange behavior is because of String Interning. And for more info about locking on interned strings see [this SO question](http://stackoverflow.com/questions/6983714/locking-on-an-interned-string) – Scott Chamberlain Jun 17 '15 at 05:13
  • 1
    Strings with the same value are not necessarily in the same memory location. The compiler will automatically compile `"1" + "2"` as `"12"`, but the `a + "2"` is created at run-time resulting in a different string even though it has the same value. Hence you're locking on a different variable. – Enigmativity Jun 17 '15 at 05:16
  • @ScottChamberlain - I don't believe it has anything to do with interning. Do you have a link to a reference source? – Enigmativity Jun 17 '15 at 05:18
  • @ScottChamberlain Right, I don't use String.Intern until now! please note comment as answer. – Reza ArabQaeni Jun 17 '15 at 05:49
  • @Enigmativity: the only reason that compilation of `"1" + "2"` _can_ be the same string instance as `"12"` is specifically because of string interning. I.e. the compiler statically interns at compile-time all identical string literals to be the same object. This can _also_ be done at runtime, but either way interning is behind the behavior. – Peter Duniho Jun 17 '15 at 06:58

1 Answers1

0

If the code is changed to:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        lock ("12")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}

then "inner lock" will be printed 10 seconds after "outter lock".

Which means "1" + "2" is literally equal to "12".

However, if using .NET Reflector to open the IL code for:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}

The IL will show following code for the outer lock

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] bool flag,
        [1] string str,
        [2] bool flag2)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: ldstr "12"
    L_0008: dup 
    L_0009: stloc.1 
    L_000a: ldloca.s flag
    L_000c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
    L_0011: nop 
    L_0012: nop 
    L_0013: ldstr "outer lock"
    L_0018: call void [mscorlib]System.Console::WriteLine(string)

The IL code:

L_0003: ldstr "12"
L_0008: dup 
L_0009: stloc.1 
L_000a: ldloca.s flag

eventually save "12" into local varaible

The IL code for "inner lock" is

.method private hidebysig static void <Main>b__3() cil managed
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
    .maxstack 2
    .locals init (
        [0] string str,
        [1] bool flag,
        [2] string str2,
        [3] bool flag2)
    L_0000: nop 
    L_0001: ldstr "1"
    L_0006: stloc.0 
    L_0007: ldc.i4.0 
    L_0008: stloc.1 
    L_0009: ldloc.0 
    L_000a: ldstr "2"
    L_000f: call string [mscorlib]System.String::Concat(string, string)
    L_0014: dup 
    L_0015: stloc.2 
    L_0016: ldloca.s flag
    L_0018: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)

The IL code:

L_0001: ldstr "1"
L_0006: stloc.0 
L_0007: ldc.i4.0 
L_0008: stloc.1 
L_0009: ldloc.0 
L_000a: ldstr "2"
L_000f: call string [mscorlib]System.String::Concat(string, string)

store "1" in a local variable, and store "2" in another local variable. Then call String.Concat.

If you try another code: (in another console program)

var c = "1" + "2";
var d = c + "2";
Console.WriteLine(string.IsInterned(d));

var e = "12";
Console.WriteLine(string.IsInterned(e));

You will find the first string.IsInterned(d) return nothing, but second string.IsInterned(e) will print "12" in console.

Because c + "2" is not literally equal to "1" + "2", but "12" is literally equal to "12".

Which means even c + "2" will return you "12", but internally they are different expression. Which means your original second "lock (a + "2")" is trying to lock on a different expression, and that's why your second code block will be executed immediately.

Jeff Chen
  • 736
  • 1
  • 8
  • 20