【问题标题】:Confusion about the lock statement in C#关于 C# 中的 lock 语句的困惑
【发布时间】:2012-03-26 03:49:02
【问题描述】:

这是来自 MSDN: lock 关键字确保一个线程不会进入临界区代码,而另一个线程却在临界区

临界区是否必须与临界区相同?

或者它的意思是: lock 关键字可确保一个线程不会进入任何由代码对象保护的临界区,而另一个线程不会进入由同一对象保护的任何临界区。 ?

    class Program
{
    static void Main(string[] args)
    {
        TestDifferentCriticalSections();

        Console.ReadLine();
    }

    private static void TestDifferentCriticalSections()
    {
        Test lo = new Test();

        Thread t1 = new Thread(() =>
        {
            lo.MethodA();
        });
        t1.Start();

        Thread t2 = new Thread(() =>
        {
            lo.MethodB();
        });
        t2.Start();
    }
}

public class Test
{
    private object obj = new object();

    public Test()
    { }

    public void MethodA()
    {
        lock (obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("A");
            }
        }
    }

    public void MethodB()
    {
        lock (obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("B");
            }
        }
    }
}

【问题讨论】:

标签: c# multithreading locking block critical-section


【解决方案1】:

你给一个对象加了锁。如果另一个线程同时尝试访问由该对象标记的临界区,它将阻塞,直到锁被移除/完成。

例子:

public static object DatabaseLck= new object();

lock (DatabaseLck) {
        results = db.Query<T>(query).ToList();
     }

或者

lock (DatabaseLck) {
       results = db.Query<T>(string.Format(query, args)).ToList();
  }

这些代码块中的任何一个都不能同时运行,因为它们使用相同的锁对象。如果您为每个使用不同的锁定对象,它们可以同时运行。

【讨论】:

  • "如果另一个线程同时尝试访问该对象,它将阻塞" -- 你的意思是:"如果另一个线程同时尝试访问由该对象标记的临界区,它会阻塞”?
【解决方案2】:

不,这意味着另一个线程不会进入受此锁语句保护的临界区。

Critical 部分仅由程序员定义,在这种情况下,您可以将其替换为:受锁保护的部分

所以翻译: lock 关键字确保一个线程不会进入受lock 保护的代码段,而另一个线程在这段代码中(受lock 保护)

【讨论】:

  • 不应该是同一个锁对象保护的任何节吗?
  • 不是,因为你在锁中使用的对象只是用来保证锁的。
  • @squelos : 锁定同一对象的任何部分都被该对象上的锁定阻止。
【解决方案3】:

这是一个相同的临界区。

lock (synclock)
{
  // the critical section protected by the lock statement
  // Only one thread can access this at any one time
}

请参阅 MSDN 上的 lock Statement

lock 关键字通过获取给定对象的互斥锁、执行语句然后释放锁来将语句块标记为临界区。


或者它的意思是:lock 关键字确保一个线程不进入任何代码的关键部分,而另一个线程在任何关键部分。 ?

。这并不意味着。这意味着由该锁保护的关键部分,并且仅该锁。


更新,如下代码示例:

如果您使用单个对象锁定,它将锁定所有个临界区,导致其他线程阻塞直到被释放。在您的代码示例中,一旦输入了MethodA 中的锁,所有其他线程到达该锁并且MethodB 上的锁将阻塞,直到锁被释放(发生这种情况是因为您在两者中都锁定了同一个对象方法)。

【讨论】:

    【解决方案4】:

    它所说的临界区是由 lock 语句保护的部分。

    任何锁定在同一对象上的关键部分都将被阻止访问。

    锁对象是静态的也很重要,因为锁需要在锁对象的同一个实例上锁定(或尝试锁定)。

    【讨论】:

    • 不要使用双重检查锁定,除非您在正确锁定方面存在性能问题。 有大约一百万种方法可以使双重检查锁定错误,而且只有一种方法把它做对;如果不小心使用,这是一种极其危险的模式。如果您认为应该使用双重检查锁定,请再想一想。您可能可以通过 (1) 常规锁定、(2) 互锁交换、(3) Lazy 类、(4) 利用静态类初始化程序锁定语义来解决您的问题。
    • @EricLippert :我一直认为双重检查锁定是必要的。感谢您的澄清。我已经从我的回答中删除了该评论,但我认为其余的仍然受到审查。
    • 不,双重检查锁定是一种用于性能优化的低锁定技术,并且与任何低锁定技术一样,非常危险。
    • @EricLippert 那么单例的锁定技术是什么,您检查对象是否为空,锁定,然后再次检查对象是否为空,然后实例化?那还是双重检查锁吗?是否需要内存屏障才能正常工作?
    • 有关双重检查锁定的危险的其他想法,请参阅stackoverflow.com/a/5821201/88656
    【解决方案5】:

    这并不意味着任何,尽管您可以通过用同一个对象锁定两个代码块来保护它们不被多个线程同时输入。这是一个常见的范例——您可能希望锁定您的集合以进行清除和写入。

    【讨论】:

      【解决方案6】:

      这个问题的措辞令人困惑,到目前为止的答案也不是特别清楚。让我把这个问题改写成几个问题:

      (1) lock 语句是否保证任何时候lock 语句的主体中不超过一个线程?

      。例如:

      static readonly object lock1 = new object();
      static readonly object lock2 = new object();
      static int counter = 0;
      static object M()
      {
          int c = Interlocked.Increment(ref counter);
          return c % 2 == 0 ? lock1 : lock2;
      }
      
      ...
      lock(M()) { Critical(); }
      

      有可能两个线程同时在 lock 语句的主体中,因为 lock 语句锁定在两个不同的对象上。线程 Alpha 可以调用 M() 并获取 lock1,然后线程 Beta 可以调用 M() 并获取 lock2。

      (2) 假设我的 lock 语句总是锁定在同一个对象上,lock 语句是否确保在任何时候都不超过一个“活动”线程在锁的主体中?

      是的。如果你有:

      static readonly object lock1 = new object();
      ...
      lock(lock1) { Critical(); }
      

      那么线程 Alpha 就可以拿到锁了,线程 Beta 会阻塞直到锁可用才进入锁体。

      (3) 假设我有两个 lock 语句,并且两个 lock 语句每次都锁定在同一个对象上,那么 lock 语句是否确保在任何一个锁的主体中都不超过一个“活动”线程时间?

      是的。如果你有:

      static readonly object lock1 = new object();
      ...
      static void X() 
      {
          lock(lock1) { CriticalX(); }
      }
      static void Y() 
      {
          lock(lock1) { CriticalY(); }
      }
      

      那么如果线程 Alpha 在 X 中并获得锁,而线程 Beta 在 Y 中,那么线程 Beta 将阻塞直到锁可用之前进入锁体。

      (4) 为什么要把“活跃”放在“吓人的引号”中?

      提请注意一个等待线程可能在锁体中的事实。您可以使用Monitor.Wait 方法“暂停”锁体中的线程,并允许被阻塞的线程激活并进入该锁体(或锁定同一对象的不同锁体)。等待线程将保持其“等待”状态,直到脉冲。在脉冲之后的某个时间,它重新加入“就绪”队列并阻塞,直到锁中没有“活动”线程。然后从中断的地方继续。

      【讨论】:

      • 与往常一样,您的回答在彻底性和准确性方面远远超过我的回答。为此,我将向您投赞成票。
      • 再一次,我受益于阅读您的一个答案。谢谢埃里克。
      • +1 用于将 active 放在可怕的引号中。 ;-) 这里非常需要。
      猜你喜欢
      • 2021-09-23
      • 2022-01-05
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多