锁定C#代表写入是否保证线程安全?

锁定C#代表写入是否保证线程安全?,c#,multithreading,delegates,C#,Multithreading,Delegates,C#委托是不可变的,因此当您从委托中添加或删除函数时,它将被一个新对象替换 考虑到以下代码: lock(shared_instance.my_delegate){ shared_instance.my_delegate+=Foo; } 考虑以下情况: 线程1和2到达锁,线程2阻塞代理实例,我们称之为A 线程1将A替换为新实例B,并退出锁 线程2是否在B或A上获得锁?如果它在A上,那么在我看来,线程3可能会出现,在线程2获得A上的锁的同时获得B上的锁,并且它们可以同时尝试覆盖删除,并且会发生

C#委托是不可变的,因此当您从委托中添加或删除函数时,它将被一个新对象替换

考虑到以下代码:

lock(shared_instance.my_delegate){
  shared_instance.my_delegate+=Foo;
}
考虑以下情况:

线程1和2到达锁,线程2阻塞代理实例,我们称之为
A

线程1将
A
替换为新实例
B
,并退出锁

线程2是否在
B
A
上获得锁?如果它在
A
上,那么在我看来,线程3可能会出现,在线程2获得
A
上的锁的同时获得
B
上的锁,并且它们可以同时尝试覆盖删除,并且会发生竞争,对吗

编辑:

我现在意识到这个问题可以推广到任何参考类型:

lock(shared_ref){
  shared_ref=different_ref;
}
基本上我想知道的是:如果发生这种情况,等待的线程仍然会锁定旧引用,是吗

线程2是在B上还是在A上获得锁?如果它在A上,那么在我看来,线程3可以出现,在线程2获得A上的锁的同时获得B上的锁,它们可以同时尝试覆盖该数据集,并发生竞争,对吗

在这两种情况下,你都有比赛条件。您的代码中有一个竞态条件,如果您使用专用对象锁定,则有一个竞态条件,如果您根本没有
lock
,则有相同的竞态条件。在所有这三种情况下,一个线程将设置该值并立即覆盖它,另一个线程将设置第二个值并使其保持不变。在所有这些情况下,哪个线程“获胜”是不确定的。在这些情况下,除了将值设置为两个可能值之一之外,不会发生任何其他情况。

查看此代码:

lock(shared_ref){
  shared_ref=different_ref;
}
lock(shared\u ref)
将锁定shared\u ref变量内部的引用,替换变量值不会更改锁定,并且在
lock{}
块结束时,旧引用将被释放。因此,如果任何其他线程在被更改之前,但在被锁定期间,在共享_ref上锁定,锁仍将被释放。第三个线程可能会在新引用已设置时锁定该引用,并且会发生设置变量的争用,因为线程2将被释放,而线程3从不在锁处等待,因为没有人持有该引用。因此线程2和线程3将竞相设置新变量

我刚刚编写了一些示例代码,我认为这非常清楚:

public class TestClass
{
    public static object myObject;

    public static void setObject(object newValue, string thread)
    {
        lock(myObject)
        {
            Debug.Print(thread+" reached the inside of the lock");
            Thread.Sleep(1000);
            myObject = newValue;
            Debug.Print(thread + " myObject was set");
            Thread.Sleep(1000);
        }
        Debug.Print(thread + " lock released");
    }

    public static void Test()
    {
        myObject = new object();
        Thread t1 = new Thread(t1_run);
        Thread t2 = new Thread(t2_run);
        Thread t3 = new Thread(t3_run);
        t1.Start();
        t2.Start();
        t3.Start();
    }

    private static void t1_run()
    {
        setObject(new object(), "t1");
    }

    private static void t2_run()
    {
        Thread.Sleep(500); // 500 millisecods so it will be locked on the old
        setObject(new object(), "t2");
    }

    private static void t3_run()
    {
        Thread.Sleep(1500); // just make sure myObject was replaced
        setObject(new object(), "t3");
    }
}
现在很明显,结果是:

t1 reached the inside of the lock
t1 myObject was set
t3 reached the inside of the lock
t1 lock released
t2 reached the inside of the lock
t3 myObject was set
t2 myObject was set
t3 lock released
t2 lock released

因为睡眠确保t2和t3设置myObject的顺序。但是,当时间非常接近时,这一点无法保证

不要锁定
共享的\u实例。我的\u委托
。为此使用专用变量,通常只是构造函数中初始化的
对象。这将解决您的问题并消除所有歧义。因为您使用了
my_delegate+=
,my_delegate实际上是一个事件,对吗?你应该看看这个答案:。也不要锁定事件,默认情况下它们为空。简而言之:event+=处理程序与event.add一样是线程安全的,不会替换锁(与-=和event.remove相同)。另外:可以显示更多代码吗?像类的整个示例一样,
shared\u实例
是什么?我可以解释给你听then@FrankJ我知道,我不打算这么做,只是激起了我的兴趣。好吧,我明白了,我很确定你的假设是正确的,这也是为什么你永远不应该锁定一个可能会改变的对象(没有适当的安全交换)。线程获取引用的锁,而不是字段的锁。这意味着,如果字段发生更改,线程仍会锁定旧字段值我认为锁会引入内存障碍,因此,至少在委托情况下,会加载新值。对于简单引用,锁可能无论如何都是不必要的,因为无论发生什么情况,只会保留一个值,而IIRC,引用是原子的。重新排序可能会发生,但在这一点上,这似乎无关紧要。@Tomeamis是的,
锁确实引入了内存障碍。孤立地看这一部分会使分析应该做什么特别困难。也许对共享变量的锁定是必要的,也许一个内存屏障就足够了,或者可能完全需要发生其他事情。一般来说,虽然锁定单个变量赋值是代码的味道,但在少数情况下它是有意义的。但通常有一个更大的操作需要锁定。