如何锁定几个对象?

时间:2011-05-12 09:01:01

标签: c#

我想同时锁定两个对象。 为什么我不能像这样的代码一样写?

lock(obj1,obj2)

我应该总是这样写吗?

lock (obj1)
{
    lock (obj2)
    {
    }
}

这可能会变得更简单吗? 可能最好引入特殊的私有对象并将其用于锁定...

9 个答案:

答案 0 :(得分:35)

嗯,这个问题太旧了,但是,我发现这是一个紧凑的问题,两个代码都会以相同的编译语句结尾(这和问题描述中的一个):

    lock (obj1) lock (obj2)
    {
        // your code
    }

答案 1 :(得分:29)

这是锁定多个对象的正确方法,是的。

我的猜测是,只允许锁定语句的单个参数的原因是使锁定的顺序尽可能清楚。

请注意,您必须确保在代码中的每个位置都以相同的顺序获取两个锁,否则您可能会出现死锁。

您也可以按照建议引入一个专用的锁定对象,但这会使您的锁定更加粗糙。这一切都取决于您的需求。如果您有时只需要其中一个锁,则应将它们分开(但请确保保留锁定顺序,如上所述)。

答案 2 :(得分:16)

如果您编写此类代码,则需要确保始终按此顺序锁定这两个对象。否则,您可能会遇到死锁。

答案 3 :(得分:5)

我遇到了同样的问题,写下了可能会帮助你的片段,即使它远非完美:

private void MultiLock(object[] locks, WaitCallback pFunc, int index = 0)
{
    if (index < locks.Count())
    {
        lock (locks[index])
        {
            MultiLock(locks, pFunc, index + 1);
        }
    }
    else
    {
        ThreadPool.QueueUserWorkItem(pFunc);
    }
}

然后,就像这样调用这个方法:

public object LockedObject1 = new Object();
public object LockedObject2 = new Object();

public void MyFunction(object arg)
{
    WaitCallback pFunc = delegate
    {
        // Operations on locked objects
    }

    MultiLock(new object[] {LockedObject1, LockedObject2}, pFunc);
}

答案 4 :(得分:2)

你在编写它时必须这样做的原因是因为你 不能 同时锁定两个对象;你一个接一个地锁定它们(并且保持锁的顺序非常重要,否则你可能会遇到死锁),并且最好尽可能明确地使用这些东西。

答案 5 :(得分:1)

简答:

所以在工程方面的区别在于,当你想让对象算作一个单一的锁和只为此,你不想让其他程序员在锁发生之间执行任何代码时,你应该使用-line 锁定,它将防止将代码放在锁之间。另一方面,如果您想达到相反的效果并允许在锁之间修改集合或执行某些操作,那么您应该使用嵌套锁。但在技术术语中,它们将编译为相同的 IL 代码。 因此,差异存在,但不是技术性的。当您编写代码时,它被置于预编译时间。

详细回答:

据我所知,在锁内部,它的工作原理类似于引用相等。如果引用等于它意味着它锁定在同一个对象上。这就是为什么不允许值类型和动态对象一样被锁定(因为它们可能会更改它们所引用的内容,而您将失去锁定)。

另一方面,2021th 编译背后的底层机制是相同的,因此这只是您希望如何使用它的糖,但每种糖都有其自身的成本,这不是排除。

>

我想分享我的一小段代码,以提高对底层锁定机制的理解

private class Tes
        {
            private object lock1 = new object();
            private object lock2 = new object();
            public void Test()
            {
                lock(lock2) lock (this.lock1)
                {
                    Console.WriteLine("lol");
                }
                lock(lock2)
                {
                    lock(lock1)
                    {
                        Console.WriteLine("lol2");
                    }
                }
            }
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var k = new Tes();
            k.Test();

        }
}

在幕后,它被编译为 IL(称为 CIL - 通用解释语言),后者被编译为机器指令,因此我们假设 IL 片段是否匹配而不是执行相同。

这就是它的编译方式。第一块锁

    lock(lock2) lock (this.lock1)
      {
        Console.WriteLine("lol");
      }

编译成

IL_0001: ldarg.0      // this
      IL_0002: ldfld        object ConsoleApp1.Program/Tes::lock2
      IL_0007: stloc.0      // V_0
      IL_0008: ldc.i4.0
      IL_0009: stloc.1      // V_1
      .try
      {
        IL_000a: ldloc.0      // V_0
        IL_000b: ldloca.s     V_1
        IL_000d: call         void [System.Threading]System.Threading.Monitor::Enter(object, bool&)
        IL_0012: nop

        // [13 17 - 13 34]
        IL_0013: ldarg.0      // this
        IL_0014: ldfld        object ConsoleApp1.Program/Tes::lock1
        IL_0019: stloc.2      // V_2
        IL_001a: ldc.i4.0
        IL_001b: stloc.3      // V_3
        .try
        {
          IL_001c: ldloc.2      // V_2
          IL_001d: ldloca.s     V_3
          IL_001f: call         void [System.Threading]System.Threading.Monitor::Enter(object, bool&)
          IL_0024: nop

          // [14 5 - 14 6]
          IL_0025: nop

          // [15 6 - 15 31]
          IL_0026: ldstr        "lol"
          IL_002b: call         void [System.Console]System.Console::WriteLine(string)
          IL_0030: nop

          // [16 5 - 16 6]
          IL_0031: nop
          IL_0032: leave.s      IL_003f
        } // end of .try
        finally
        {

          IL_0034: ldloc.3      // V_3
          IL_0035: brfalse.s    IL_003e
          IL_0037: ldloc.2      // V_2
          IL_0038: call         void [System.Threading]System.Threading.Monitor::Exit(object)
          IL_003d: nop

          IL_003e: endfinally
        } // end of finally

        IL_003f: leave.s      IL_004c
      } // end of .try
      finally
      {

        IL_0041: ldloc.1      // V_1
        IL_0042: brfalse.s    IL_004b
        IL_0044: ldloc.0      // V_0
        IL_0045: call         void [System.Threading]System.Threading.Monitor::Exit(object)
        IL_004a: nop

        IL_004b: endfinally
      } // end of finally

以及第二块 c# 代码

lock(lock2)
    {
        lock(lock1)
            {
                Console.WriteLine("lol2");
            }
    }

编译成相同的 IL(比较以确保,我已经比较过,但它会让你更深入地了解正在发生的事情)

 // [17 5 - 17 16]
      IL_004c: ldarg.0      // this
      IL_004d: ldfld        object ConsoleApp1.Program/Tes::lock2
      IL_0052: stloc.s      V_4
      IL_0054: ldc.i4.0
      IL_0055: stloc.s      V_5
      .try
      {
        IL_0057: ldloc.s      V_4
        IL_0059: ldloca.s     V_5
        IL_005b: call         void [System.Threading]System.Threading.Monitor::Enter(object, bool&)
        IL_0060: nop

        // [18 5 - 18 6]
        IL_0061: nop

        // [19 6 - 19 17]
        IL_0062: ldarg.0      // this
        IL_0063: ldfld        object ConsoleApp1.Program/Tes::lock1
        IL_0068: stloc.s      V_6
        IL_006a: ldc.i4.0
        IL_006b: stloc.s      V_7
        .try
        {
          IL_006d: ldloc.s      V_6
          IL_006f: ldloca.s     V_7
          IL_0071: call         void [System.Threading]System.Threading.Monitor::Enter(object, bool&)
          IL_0076: nop

          // [20 6 - 20 7]
          IL_0077: nop

          // [21 7 - 21 33]
          IL_0078: ldstr        "lol2"
          IL_007d: call         void [System.Console]System.Console::WriteLine(string)
          IL_0082: nop

          // [22 6 - 22 7]
          IL_0083: nop
          IL_0084: leave.s      IL_0093
        } // end of .try
        finally
        {

          IL_0086: ldloc.s      V_7
          IL_0088: brfalse.s    IL_0092
          IL_008a: ldloc.s      V_6
          IL_008c: call         void [System.Threading]System.Threading.Monitor::Exit(object)
          IL_0091: nop

          IL_0092: endfinally
        } // end of finally

        // [23 5 - 23 6]
        IL_0093: nop
        IL_0094: leave.s      IL_00a3
      } // end of .try
      finally
      {

        IL_0096: ldloc.s      V_5
        IL_0098: brfalse.s    IL_00a2
        IL_009a: ldloc.s      V_4
        IL_009c: call         void [System.Threading]System.Threading.Monitor::Exit(object)
        IL_00a1: nop

        IL_00a2: endfinally
      } // end of finally

唯一的区别是在里面

IL_0007: stloc.0

函数,但这只是来自堆栈顶部的 getter,这取决于代码的放置位置,因为我已将所有代码移到一个类中并同步执行 - 全部在堆栈上。

>

但是技术匹配的意思并不意味着在实践中是一样的,因为你无法将日志放在单行锁之间,所以你不能确保你知道哪里是正确的

lock(obj1) lock(obj2) { Console.WriteLine(""); } //you are sure after both, but //you are unable to catch the space between them

另一种处理锁之间中间空间的方法

lock(obj1){
  Console.WriteLine("do something after first lock");
  lock(obj2) {
    //you are clearly know when the first lock and the second lock appers
  }
}

答案 6 :(得分:0)

您可以创建名为PadLock或类似物品的专用对象,而不是锁定对象本身,而只将其锁定在需要它的地方。

答案 7 :(得分:0)

此处锁定并不意味着在锁定期间,其他线程上的其他代码无法访问或修改该对象。如果锁定对象,则任何其他线程可以同时修改该对象。锁代码块允许您执行的操作是使锁定块内的代码成为单个条目,即只有一个线程可以执行一次锁定代码块,而尝试执行相同代码块的其他线程将不得不等到所有者线程完成执行代码块。所以基本上你通常不需要在通常情况下锁定2个或更多对象。通过锁定你的目的是使代码块单条输入

答案 8 :(得分:0)

执行类似

的操作
    internal static void DuoEnter(object po1, object po2, int pnTimeOutMs = 1000)
    {
        if ((po1 == null) && (po2 == null))
            return;
        int nMaxLoops = 100 * pnTimeOutMs;
        bool lOneProcessor = Environment.ProcessorCount < 2;
        for (int nLoops = 0; nLoops < nMaxLoops; nLoops++)
        {
            if ((po1 == null) || (po2 == null) || (po1 == po2))
            {
                if (Monitor.TryEnter(po1 ?? po2))
                    return;
            }
            else
            {
                if (Monitor.TryEnter(po1))
                    if (Monitor.TryEnter(po2))
                        return;
                    else
                        Monitor.Exit(po1);
            }
            if (lOneProcessor || (nLoops % 100) == 99)
                Thread.Sleep(1); // Never use Thread.Sleep(0)
            else
                Thread.SpinWait(20);
        }
        throw new TimeoutException(
            "Waited more than 1000 mS trying to obtain locks on po1 and po2");
    }

    internal static void DuoExit(object po1, object po2)
    {
        if ((po1 == null) && (po2 == null))
            return;
        if (po1 == null || po2 == null || po1 == po2)
            Monitor.Exit(po2 ?? po1);
        else
        {
            Monitor.Exit(po2);
            Monitor.Exit(po1);
        }
    }