锁定传递对象会发生什么?

时间:2016-01-04 22:27:54

标签: c# multithreading locking

在一个正在玩线程的项目中。我试图建立一个不会“破坏”数据的安全线程。我的线程在后台运行,并在另一个类上调用函数,我可以调用Go和Go2,一个函数添加,一个从列表中删除。我不希望它们同时运行,以下情况之间有什么区别:

static readonly object _locker1 = new object();
static readonly object _locker2 = new object();


public void Go(Object something)
{
  lock (_locker1)
  {
    myList.add(something);
  }
}

public void Go2(Object something)
{
  lock (_locker2)
  {
    myList.Remove(something);
  }
}

如果我用以下代替Go2:

public void Go2(Object something)
{
  lock (_locker1)
  {
    myList.Remove(something);
  }
}

请注意锁定参数。

第三种情况可以帮助我理解,假设我从一个不同的线程(thread2)调用Go,它可以运行,因为_locker1被thread2锁定而Go2(它具有被thread2锁定的_locker 1)是从线程1?

static readonly object _locker1 = new object();
static readonly object _locker2 = new object();


public void Go(Object something)
{
  lock (_locker1)
  {
    //Can I call Go2 which is locked by the same object?
    Go2(something);
  }
}

public void Go2(Object something)
{
  lock (_locker1)
  {
    myList.Remove(something);
  }
}

有人可以解释传递给lock的值是什么吗?

4 个答案:

答案 0 :(得分:5)

这非常简单:如果两个锁使用同一个对象,它们将不会同时运行。在你的第一个片段中,当Go和Go2锁定不同的对象时,它们可以同时运行并做坏事。

答案 1 :(得分:2)

正如documentation所说:

  

lock关键字通过获取给定对象的互斥锁,执行语句,然后释放锁定,将语句块标记为关键部分。

因此,

lock是给定对象的监视器的语法糖。当您使用第一个代码示例时,如果两个线程都执行Go(或Go2),则它们必须等待彼此,而如果两个执行不同的方法,则可能导致同步冲突。 / p>

在第二个代码示例中,由于您始终锁定解锁同一个对象,因此可以确保不能同时执行myList.addmyList.remove

编辑:如遇第三种情况,recursive locks are counted。这意味着如果第一个线程调用Go而第二个线程调用Go2,如果第一个线程首先进入lock,它将调用Go2获取访问锁,删除项,从递归调用返回,离开lock,然后第二个线程才能进入Go2的锁。如果第二个线程赢得比赛,第二个线程将首先进入lock并阻止来自外部lock的第一个线程。只有当第二个线程离开lock Go2时,第一个线程才能进入lock的{​​{1}}(并执行递归调用)。

答案 2 :(得分:1)

锁定语句实际上转换为引擎盖下的监视器锁,例如:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}

要了解有关Monitor锁定工作方式的更多信息,请查看此处:MSDN Documentation

引用MSDN:

  

使用Enter获取作为参数传递的对象的Monitoron。如果另一个线程在对象上执行了Enter但尚未执行相应的Exit,则当前线程将阻塞,直到另一个线程释放该对象。如果没有阻塞,同一个线程不止一次调用Enter是合法的;但是,在等待对象的其他线程将解除阻塞之前,必须调用相同数量的Exit调用。   使用Monitor锁定对象(即引用类型),而不是值类型。将值类型变量传递给Enter时,它将被装箱为对象。如果再次将同一个变量传递给Enter,则将其作为单独的对象加框,并且该线程不会阻塞。在这种情况下,Monitor应该保护的代码不受保护。此外,当您将变量传递给Exit时,仍会创建另一个单独的对象。因为传递给Exit的对象与传递给Enter的对象不同,所以Monitor会抛出SynchronizationLockException。有关更多信息,请参阅概念主题监视器。

这意味着您的代码将被阻止在2个不同的对象上,您只想阻止它们使其线程安全。

答案 3 :(得分:1)

传递给lock的值是将在块(花括号)中访问lock语句的共享状态的象征。锁定该值的每个请求都将按顺序排队和处理。一次只允许一个请求者处理该值,因此共享状态一次只能被一个请求者访问。

  

<强>抽象

     

如果我和一只橡皮鸡会面,我就说#34;只有   持橡皮鸡的人可以说&#34;。在这   场景中,橡皮鸡是锁参数,能力   说话是共享资源。每个想发言的人都会形成一个   线。只有一个人可以抓鸡,所以只有一个人可以   说话。当说话的人完成后,他们将鸡肉交给了   排在第二位的人。

在您的第一种情况下,您有两只橡胶鸡:储物柜1(橡皮鸡1)和储物柜2(橡皮鸡2)。因此,Go和Go2不会在一个转弯处等待(他们都有一只鸡!)。调用Go的线程将能够添加到myList,而另一个调用Go2的线程可以同时访问myList以从列表中删除该项目。然而,两个线程调用Go将等待轮到他们,因为他们都需要相同的橡皮鸡:locker1;调用Go2的两个线程也是如此。

如果你让Go和Go2都使用相同的值(相同的鸡),那么他们将不得不等待他们获得该值的锁定。这将阻止他们一个调用Go的线程和另一个调用Go2的线程同时访问myList。