线程安全使用单身成员

时间:2008-09-03 20:29:16

标签: c# .net multithreading singleton thread-safety

我有一个多个类使用的C#单例类。通过Instance访问Toggle()方法线程安全吗?如果是,则通过什么假设,规则等。如果不是,为什么我该如何解决?

public class MyClass
{
    private static readonly MyClass instance = new MyClass();

    public static MyClass Instance
    {
        get { return instance; }
    }

    private int value = 0;

    public int Toggle()
    {
        if(value == 0) 
        {
            value = 1; 
        }
        else if(value == 1) 
        { 
            value = 0; 
        }

        return value;
    }
}

9 个答案:

答案 0 :(得分:28)

  

通过'Instance'访问'Toggle()'类threadafe?如果是,通过什么假设,规则等等。如果不是,为什么以及如何解决它?

不,这不是线程安全的。

基本上,两个线程可以同时运行Toggle函数,因此可能会发生这种情况

    // thread 1 is running this code
    if(value == 0) 
    {
        value = 1; 
        // RIGHT NOW, thread 2 steps in.
        // It sees value as 1, so runs the other branch, and changes it to 0
        // This causes your method to return 0 even though you actually want 1
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    return value;

您需要按照以下假设进行操作。

如果2个线程正在运行,它们可以并且将在任何点随机交错并相互交互。您可以在写入或读取64位整数或浮点数(在32位CPU上)的一半,另一个线程可以跳入并从您下面更改它。

如果2个线程永远不会访问任何共同点,那么无关紧要,但只要它们这样做,就需要防止它们踩到彼此的脚趾。在.NET中执行此操作的方法是使用锁。

你可以通过思考这样的事情来决定锁定的内容和位置:

对于给定的代码块,如果something的值从我的下方改变了,那会有关系吗?如果愿意,您需要在代码的持续时间内锁定something

再看一下你的例子

    // we read value here
    if(value == 0) 
    {
        value = 1; 
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    // and we return it here
    return value;

为了使其返回我们期望的内容,我们假设value不会在读取和return之间发生变化。为了使这个假设真正正确,您需要在该代码块的持续时间内锁定value

所以你要这样做:

lock( value )
{
     if(value == 0) 
     ... // all your code here
     return value;
}

<强>无论其

在.NET中,您只能锁定引用类型。 Int32是一种值类型,因此我们无法锁定它 我们通过引入一个“虚拟”对象来解决这个问题,并在我们想要锁定“值”的任何地方锁定

这是Ben Scheirman所指的内容。

答案 1 :(得分:8)

正如Ben指出的那样,原始的补充不是线程安全的

使线程安全的一种简单方法是引入一个lock语句。例如。像这样:

public class MyClass
{
    private Object thisLock = new Object();
    private static readonly MyClass instance = new MyClass();
    public static MyClass Instance
    {
        get { return instance; }
    }
    private Int32 value = 0;
    public Int32 Toggle()
    {
        lock(thisLock)
        {
            if(value == 0) 
            {
                value = 1; 
            }
            else if(value == 1) 
            { 
                value = 0; 
            }
            return value;
        }
    }
}

答案 2 :(得分:2)

您的线程可能会在该方法的中间停止并将控制转移到另一个线程。您需要围绕该代码的关键部分...

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}

答案 3 :(得分:2)

  

这就是我的想法。但是,我,我   寻找细节......'切换()'   不是静态方法,但它是一个   静态属性的成员(何时   使用'Instance')。是什么造成的   它在线程之间共享?

如果你的应用程序是多线程的,你可以预见多个线程将访问该方法,这使得它在线程之间共享。因为您的类是Singleton,所以您知道不同的线程将访问SAME对象,因此请注意您的方法的线程安全性。

  

这又如何适用于单身人士   一般来说。我必须解决吗?   在我班上的每一种方法中都有这个吗?

正如我上面所说,因为它是一个单独的你知道不同的线程将可能同时访问同一个对象。这并不意味着您必须使每个方法获得锁定。如果您注意到一个simultaneousos调用可能导致类的损坏状态,那么您应该应用@Thomas提到的方法

答案 4 :(得分:2)

  

我可以假设单例模式将我可爱的线程安全类暴露给常规静态成员的所有线程问题吗?

没有。你的班级根本不是线程安全的。单身人士与此毫无关系。

  

(我正在了解实例成员调用静态对象导致线程问题的事实)

这与此无关。

你必须这样想:在我的程序中,2个(或更多)线程是否可以同时访问这个数据?

通过单例或静态变量获取数据或将对象作为方法参数传入的事实无关紧要。在一天结束时,它只是PC的RAM中的一些位和字节,重要的是多个线程是否可以看到相同的位。

答案 5 :(得分:1)

我还要向MyClass添加一个受保护的构造函数,以防止编译器生成公共默认构造函数。

答案 6 :(得分:1)

  

我在想,如果我转储单例模式并强制每个人获得该类的新实例,它将缓解一些问题......但这并不能阻止其他任何人初始化该类型的静态对象并传递周围...或从多个线程旋转,所有访问来自同一实例的'Toggle()'。

Bingo: - )

  

我现在明白了。这是一个艰难的世界。我希望我没有重构遗留代码:(

不幸的是,多线程很难,你必须对事情非常偏执:-) 在这种情况下,最简单的解决方案是坚持使用单例,并在值周围添加一个锁定,如示例所示。

答案 7 :(得分:0)

引用:

if(value == 0) { value = 1; }
if(value == 1) { value = 0; }
return value;

value将始终为0 ......

答案 8 :(得分:0)

好吧,我其实并不熟悉C#但是我对Java很好,所以我会给出答案,希望两者足够相似以至于它会有用。如果没有,我道歉。

答案是,不,这不安全。一个线程可以在另一个线程的同时调用Toggle(),尽管这个代码不太可能,但是Thread1可以在Thread2检查它和设置它的时间之间设置value。 / p>

要修复,只需创建Toggle()synchronized即可。它不会阻塞任何东西或调用任何可能产生另一个可以调用Toggle()的线程的东西,所以你需要做的就是保存它。