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

C# 锁定传递的对象会发生什么?,c#,multithreading,locking,C#,Multithreading,Locking,在一个项目中,我在玩线程。我正在尝试创建一个不会“破坏”数据的安全线程。我的线程在后台运行,并在另一个类上调用函数,我可以调用Go和Go2,一个函数从列表中添加和删除。我不希望它们同时运行,以下情况之间有什么区别: static readonly object _locker1 = new object(); static readonly object _locker2 = new object(); public void Go(Object something) { lock (_

在一个项目中,我在玩线程。我正在尝试创建一个不会“破坏”数据的安全线程。我的线程在后台运行,并在另一个类上调用函数,我可以调用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);
  }
}
请注意lock参数

第三种情况有助于我理解,假设我从另一个线程thread2调用Go,它可以运行吗?因为_locker1被thread2锁定,而具有_locker1被thread2锁定的Go2从thread1调用

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的值的作用吗?

很简单:如果两个锁使用同一个对象,它们将不会同时运行。在您的第一个代码片段中,由于Go和Go2锁定了不同的对象,因此它们可能同时运行并做坏事。

很简单:如果两个锁使用同一个对象,它们将不会同时运行。在您的第一个代码片段中,由于Go和Go2锁定在不同的对象上,它们可能会同时运行并做坏事。

正如上面所说:

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

因此,锁是给定对象监视器的语法糖。当您使用第一个代码示例时,如果两个线程都执行Go或Go2,那么它们将不得不互相等待,而如果这两个线程执行不同的方法,则可能会导致同步冲突

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

编辑:在第三种情况下。这意味着,在第一个线程调用Go,第二个线程调用Go2的情况下,如果第一个线程首先进入锁,它将调用Go2访问锁,移除项,从递归调用返回,离开锁,然后第二个线程才能进入Go2的锁。如果第二个线程赢得比赛,第二个线程将首先进入锁并从外部锁阻止第一个线程。只有当第二个线程离开Go2锁时,第一个线程才能进入Go锁并执行递归调用。

如图所示:

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

因此,锁是给定对象监视器的语法糖。当您使用第一个代码示例时,如果两个线程都执行Go或Go2,那么它们将不得不互相等待,而如果这两个线程执行不同的方法,则可能会导致同步冲突

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


编辑:在第三种情况下。这意味着,在第一个线程调用Go,第二个线程调用Go2的情况下,如果第一个线程首先进入锁,它将调用Go2访问锁,移除项,从递归调用返回,离开锁,然后第二个线程才能进入Go2的锁。如果第二个线程赢得比赛,第二个线程将首先进入锁并从外部锁阻止第一个线程。只有当第二个线程离开Go2锁时,第一个线程才能进入Go锁并执行递归调用。

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

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}
要了解有关监视器锁工作原理的更多信息,请查看以下内容:

引用MSDN:

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

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

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

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}
要了解有关监视器锁工作原理的更多信息,请查看以下内容:

引用MSDN:

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


这意味着您的代码将被阻止在两个不同的对象上,您只希望阻止其中一个对象以使其线程安全。

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

抽象

就像我和一只橡皮鸡开了个会,我只说 拿着橡皮鸡的人会说话。在这个 场景中,橡皮鸡是锁定参数,并且能够 说话是共享的资源。每个想发言的人将组成一个小组 线只有一个人可以拿着鸡,所以只有一个人可以 谈当这个人讲完后,他们把鸡递给了那个人 排队的下一个人

在第一种情况下,您有两个橡胶鸡:locker1橡胶鸡1和locker2橡胶鸡2。因此,Go和Go2不必互相等待,他们都有一只鸡!。一个调用Go的线程可以添加到myList,而另一个调用Go2的线程可以同时访问myList以从列表中删除该项。然而,两个调用Go的线程将不得不等待轮到它们,因为它们都需要相同的橡皮鸡:locker1;这同样适用于调用Go2的两个线程


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

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

抽象

就像我和一只橡皮鸡开了个会,我只说 拿着橡皮鸡的人会说话。在这个 场景中,橡皮鸡是锁定参数,并且能够 说话是共享的资源。每个想发言的人将组成一个小组 线只有一个人可以拿着鸡,所以只有一个人可以 谈当这个人讲完后,他们把鸡递给了那个人 排队的下一个人

在第一种情况下,您有两个橡胶鸡:locker1橡胶鸡1和locker2橡胶鸡2。因此,Go和Go2不必互相等待,他们都有一只鸡!。一个调用Go的线程可以添加到myList,而另一个调用Go2的线程可以同时访问myList以从列表中删除该项。然而,两个调用Go的线程将不得不等待轮到它们,因为它们都需要相同的橡皮鸡:locker1;这同样适用于调用Go2的两个线程


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

@cecilloPardo让我们假设我调用Go,稍后调用Go2,而Go没有完成,它只是等待Go完成还是跳过Go2,因为它被锁定?在第一种情况下,Go2将同时运行。在第二种情况下,执行将在Go2的lock语句处停止,直到Go1的lock语句完成为止。@cecilo谢谢!
如果我从go in场景2中调用Go2,会不会产生死锁?死锁是由线程a锁定在_locker1上和线程B锁定在_locker2上引起的。然后,线程A请求锁定_locker2,并且在锁定_locker2之前不会释放其对_locker1的锁定。线程B则执行相反的操作:它请求锁定_locker1,并且在锁定_locker1之前不会释放它在_locker2上的锁定。因为两个线程都无法获得第二个请求的锁,因为另一个线程不会释放它,所以线程处于死锁状态。正如@CecilioPardo所说,锁位于线程级别。这是有意义的,因为并发只可能跨多个线程,而不可能在单个线程内。因此,在Go和Go2都需要相同锁的地方从Go调用Go2不会死锁;线程将调用Go,获取_locker1上的锁,然后调用Go2,并将传递lock语句,因为它已经具有所需的锁。@cecilloPardo让我们假设我调用Go,然后在Go未完成时调用Go2。它只是等待Go完成还是跳过Go2,因为它已锁定?在第一种情况下,Go2将同时运行。在第二种情况下,执行将在Go2的lock语句处停止,直到Go1的lock语句完成为止。@cecilo谢谢!如果我从go in场景2中调用Go2,会不会产生死锁?死锁是由线程a锁定在_locker1上和线程B锁定在_locker2上引起的。然后,线程A请求锁定_locker2,并且在锁定_locker2之前不会释放其对_locker1的锁定。线程B则执行相反的操作:它请求锁定_locker1,并且在锁定_locker1之前不会释放它在_locker2上的锁定。因为两个线程都无法获得第二个请求的锁,因为另一个线程不会释放它,所以线程处于死锁状态。正如@CecilioPardo所说,锁位于线程级别。这是有意义的,因为并发只可能跨多个线程,而不可能在单个线程内。因此,在Go和Go2都需要相同锁的地方从Go调用Go2不会死锁;线程将调用Go,获得_locker1上的锁,然后调用Go2,并将传递lock语句,因为它已经具有所需的锁。第三种情况如何,如果Go1是从其他线程调用的,可以调用Go2吗?第三种情况如何,如果Go1是从其他线程调用的,可以调用Go2吗?