Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/319.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在使用setter时,何时应该在多线程单例中锁定静态实例?_C#_Multithreading_Thread Safety - Fatal编程技术网

C# 在使用setter时,何时应该在多线程单例中锁定静态实例?

C# 在使用setter时,何时应该在多线程单例中锁定静态实例?,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,试图了解何时锁定静态变量被认为是最佳实践。静态实例设置程序是线程安全的吗?如果不是,应该是吗?为什么不保证线程安全会导致什么后果 class MyClass { private static MyClass _instance; private static readonly object _padlock = new object(); public static MyClass Instance { get {

试图了解何时锁定静态变量被认为是最佳实践。静态实例设置程序是线程安全的吗?如果不是,应该是吗?为什么不保证线程安全会导致什么后果

class MyClass
{
    private static MyClass _instance;

    private static readonly object _padlock = new object();

    public static MyClass Instance
    {
        get
        {
            if(_instance == null)
            {
                lock(_padlock)
                {
                    if(_instance == null)
                    {
                        _instance = new MyClass();
                    }
                }
            }
            return _instance;
        }
        set => _instance = value;
    }

}
这称为双重检查锁定

但是,双重检查锁定要求基础字段为1

简言之,分配是原子的,但需要通过跨不同内核/CPU的锁进行完全同步。另一个并发读取该值的内核可能会得到一个过时的值cached1

有几种方法可以使代码线程安全:

避免双重检查锁定,只需执行lock语句中的所有操作。 使用volatile关键字将字段设置为volatile。 使用保证线程安全的Lazy类。 注:完全不设防的二传手进一步增加了复杂性3

然而,在您的情况下,使用双重检查锁定可能会与单个检查和volatile字段锁定一起工作,但我认为您最好的选择是完全锁定所有内容并确保安全

public static MyClass Instance
{
    get
    {
         lock(_padlock)
         {
             if(_instance == null)
                 _instance = new MyClass();
             return _instance;
         }

    }
    set 
    {
         lock(_padlock)
         {
             _instance = value;
         }
    } 
}
注意:是的,它将招致性能惩罚

参考文献

一,

二,

3评论来自

额外资源

这称为双重检查锁定

但是,双重检查锁定要求基础字段为1

简言之,分配是原子的,但需要通过跨不同内核/CPU的锁进行完全同步。另一个并发读取该值的内核可能会得到一个过时的值cached1

有几种方法可以使代码线程安全:

避免双重检查锁定,只需执行lock语句中的所有操作。 使用volatile关键字将字段设置为volatile。 使用保证线程安全的Lazy类。 注:完全不设防的二传手进一步增加了复杂性3

然而,在您的情况下,使用双重检查锁定可能会与单个检查和volatile字段锁定一起工作,但我认为您最好的选择是完全锁定所有内容并确保安全

public static MyClass Instance
{
    get
    {
         lock(_padlock)
         {
             if(_instance == null)
                 _instance = new MyClass();
             return _instance;
         }

    }
    set 
    {
         lock(_padlock)
         {
             _instance = value;
         }
    } 
}
注意:是的,它将招致性能惩罚

参考文献

一,

二,

3评论来自

额外资源


在我看来,在设定器上锁定或不锁定,你总是会遇到时间问题。想象一下这些场景:

您在setter上有一个锁,但是在该锁启用之前,调用getter。调用方获取旧实例。 你在setter上有一个锁,但是在锁被锁定之后,对getter的调用就进来了。调用方等待锁释放,然后获取新实例。 您没有对setter的锁定,并且调用在您替换实例之前进入。调用方获取旧实例。 您没有对setter的锁定,并且在您替换实例之后,调用将进入。调用方获取新实例。 有锁和无锁,调用方接收哪个实例是一个时间问题

我能看到的唯一问题是,您是否希望能够将Instance设置为null。如果是这种情况,您当前的代码将无法工作,因为在If语句和返回它之间可能会更改_实例。您可以通过复制引用来解决此问题:

public static MyClass Instance
{
    get
    {
        var instanceSafeRef = _instance;
        if(instanceSafeRef == null)
        {
            lock(_padlock)
            {
                if(_instance == null)
                {
                    _instance = new MyClass();
                }
                instanceSafeRef = _instance;
            }
        }
        return instanceSafeRef;
    }
    set => _instance = value;
}

在我看来,在设定器上锁定或不锁定,你总是会遇到时间问题。想象一下这些场景:

您在setter上有一个锁,但是在该锁启用之前,调用getter。调用方获取旧实例。 你在setter上有一个锁,但是在锁被锁定之后,对getter的调用就进来了。调用方等待锁释放,然后获取新实例。 您没有对setter的锁定,并且调用在您替换实例之前进入。调用方获取旧实例。 您没有对setter的锁定,并且在您替换实例之后,调用将进入。调用方获取新实例。 有锁和无锁,调用方接收哪个实例是一个时间问题

我能看到的唯一问题是,您是否希望能够将Instance设置为null。如果是这种情况,您当前的代码将无法工作,因为在If语句和返回它之间可能会更改_实例。您可以通过复制引用来解决此问题:

public static MyClass Instance
{
    get
    {
        var instanceSafeRef = _instance;
        if(instanceSafeRef == null)
        {
            lock(_padlock)
            {
                if(_instance == null)
                {
                    _instance = new MyClass();
                }
                instanceSafeRef = _instance;
            }
        }
        return instanceSafeRef;
    }
    set => _instance = value;
}

你为什么有二传手?这样你就可以在某个时候用一个新的类替换MyClass了吗?正确。它将被用作环境上下文模式的一部分,我将在其中实现一个工厂类。我看不出锁定它的理由,但它似乎也不是线程安全的。我建议将setter隐藏在单独的、适当命名的方法后面,以阻止随机分配,因为可能会发生这种情况。。也不,它不是线程安全的。我同意

它不是线程安全的,但是任务是原子的,这让我很困惑。谢谢大家的帮助。你为什么有二传手?这样你就可以在某个时候用一个新的类替换MyClass了吗?正确。它将被用作环境上下文模式的一部分,我将在其中实现一个工厂类。我看不出锁定它的理由,但它似乎也不是线程安全的。我建议将setter隐藏在单独的、适当命名的方法后面,以阻止随机分配,因为可能会发生这种情况。。同样不,它不是线程安全的。我同意它不是线程安全的,但是原子的任务让我有点困惑。谢谢大家的帮助。我知道双重检查锁定,更喜欢使用Lazy,但这是一种罕见的需要setter的情况。在过去,我使用volatile关键字,但以int为例。所以您建议锁定setter,使其线程安全?@ChrisGessler是的,您还需要锁定setter。@MichaelRandall-您能解释一下不锁定它的负面影响吗?例如,我的内存是否会损坏?它会使exe崩溃,还是我只是接收对象的缓存旧副本?我尝试用数千个线程来破坏应用程序,但它不能。@ChrisGessler不,分配是原子的,最坏的情况是你最终得到了错误的引用,你认为你得到了默认值,但这是默认值的不同实例化,或者你认为你已经设置了默认值,但你最终得到了默认值,还是邪恶versa@MichaelRandall-我同意。安全第一!谢谢你的帮助!谢谢我知道双重检查锁定,更喜欢使用Lazy,但这是一种罕见的需要setter的情况。在过去,我使用volatile关键字,但以int为例。所以您建议锁定setter,使其线程安全?@ChrisGessler是的,您还需要锁定setter。@MichaelRandall-您能解释一下不锁定它的负面影响吗?例如,我的内存是否会损坏?它会使exe崩溃,还是我只是接收对象的缓存旧副本?我尝试用数千个线程来破坏应用程序,但它不能。@ChrisGessler不,分配是原子的,最坏的情况是你最终得到了错误的引用,你认为你得到了默认值,但这是默认值的不同实例化,或者你认为你已经设置了默认值,但你最终得到了默认值,还是邪恶versa@MichaelRandall-我同意。安全第一!谢谢你的帮助!