【问题标题】:Exclusion lock over string has strange behavior字符串上的排除锁有奇怪的行为
【发布时间】:2015-06-17 05:09:57
【问题描述】:

我遇到了令我困惑的 lock 语句问题:

如果连接两个具有相同表达式("1" + "2") 的字符串,如下所示,lock 语句将该表达式实现为字符串并按预期锁定工作:

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

但是如果在第一个lock ("1" + "2") 中更改为var a="1"; lock (a + "2")

虽然两个表达式具有相同的结果,但锁定语句将其作为两个不同的表达式处理,因此第二个锁定语句立即启动:

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

能否解释一下这种行为:

(我知道在 lock 语句中使用字符串 (MSDN) 违反了锁定准则。)

【问题讨论】:

  • 奇怪的行为是因为字符串实习。有关锁定实习字符串的更多信息,请参阅this SO question
  • 具有相同值的字符串不一定在相同的内存位置。编译器会自动将"1" + "2" 编译为"12",但a + "2" 是在运行时创建的,即使它具有相同的值也会导致不同的字符串。因此,您锁定了一个不同的变量。
  • @ScottChamberlain - 我认为这与实习无关。您有参考来源的链接吗?
  • @ScottChamberlain 对,我直到现在才使用 String.Intern!请注意评论作为答案。
  • @Enigmativity:编译"1" + "2" 可以"12" 是相同的字符串实例的唯一原因是字符串实习。 IE。编译器在编译时将所有相同的字符串文字静态实习为同一个对象。这可以在运行时完成,但无论哪种方式实习都是行为背后的原因。

标签: c# string locking


【解决方案1】:

如果代码改为:

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

那么“inner lock”会在“outer lock”之后10秒打印出来。

这意味着“1”+“2”实际上等于“12”。

但是,如果使用 .NET Reflector 打开 IL 代码:

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

IL 将显示以下外锁代码

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

IL 代码:

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

最终将“12”保存到本地变量中

“内锁”的IL代码是

.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&)

IL 代码:

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)

将“1”存储在一个局部变量中,并将“2”存储在另一个局部变量中。然后调用 String.Concat。

如果您尝试其他代码:(在另一个控制台程序中)

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

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

你会发现第一个 string.IsInterned(d) 什么都不返回,但是第二个 string.IsInterned(e) 会在控制台打印“12”。

因为c+“2”不等于“1”+“2”,而是“12”等于“12”。

这意味着即使 c + "2" 也会返回 "12",但在内部它们是不同的表达式。这意味着您原来的第二个“lock (a + "2")”正在尝试锁定不同的表达式,这就是为什么您的第二个代码块将立即执行的原因。

【讨论】:

    猜你喜欢
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 1970-01-01
    相关资源
    最近更新 更多