单例成员的线程安全使用

时间:2020-03-05 18:47:16  来源:igfitidea点击:

我有一个Csingleton类,可供多个类使用。通过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;
    }
}

解决方案

回答

引用:

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

值将始终为0 ...

回答

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

private static object _lockDummy = new object();

...

lock(_lockDummy)
{
   //do stuff
}

回答

好吧,我实际上不太了解Cthat ...但是我对Java很了解,所以我会给出答案,希望这两者足够相似,以至于很有用。如果没有,我深表歉意。

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

要解决此问题,只需将Toggle()同步化即可。它不会阻塞任何东西,也不会调用任何可能产生另一个可以调用Toggle()的线程的东西,因此我们只需保存即可。

回答

正如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;
        }
    }
}

回答

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

回答

Is access through 'Instance' to the 'Toggle()' class threadsafe? If yes, by what assumptions, rules, etc. If no, why and how can I fix it?

不,它不是线程安全的。

基本上,两个线程都可以同时运行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;

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

如果有两个线程正在运行,则它们可以并且将在任何时候随机交织并彼此交互。我们可以在写入或者读取64位整数或者浮点数(在32位CPU上)的过程中途中途,另一个线程可以跳入并从下面进行更改。

如果这两个线程从不访问任何共同点,那没关系,但是一旦它们访问,就需要防止它们互相踩到脚趾。在.NET中执行此操作的方法是使用锁。

我们可以通过考虑以下事情来决定锁定什么以及在何处锁定:

对于给定的代码块,如果'something'的值从我下面改变了,那会重要吗?如果可以的话,我们需要在重要的代码持续时间内锁定该"东西"。

再看你的例子

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

为了使它返回我们期望的结果,我们假设在读取和返回之间不会改变"值"。为了使这一假设真正正确,我们需要在该代码块的持续时间内锁定"值"。

因此,我们可以这样做:

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

然而

在.NET中,我们只能锁定引用类型。 Int32是一个值类型,因此我们无法将其锁定。
我们通过引入"虚拟"对象并将其锁定在想要锁定"值"的位置来解决此问题。

这就是本·谢尔曼(Ben Scheirman)所指的。

回答

That is what I thought. But, I I'm
  looking for the details... 'Toggle()'
  is not a static method, but it is a
  member of a static property (when
  using 'Instance'). Is that what makes
  it shared among threads?

如果应用程序是多线程的,并且可以预见到有多个线程将访问该方法,那么它将在线程之间共享。因为类是Singleton,所以我们知道不同的线程将访问SAME对象,因此请注意方法的线程安全性。

And how does this apply to singletons
  in general. Would I have to address
  this in every method on my class?

就像我在上面说的那样,因为它是一个单例,所以我们知道不同的线程可能会同时访问同一对象。这并不意味着我们必须使每个方法都获得一个锁。如果我们发现同时调用可能导致类的状态损坏,则应应用@Thomas提及的方法

回答

Can I assume that the singleton pattern exposes my otherwise lovely thread-safe class to all the thread problems of regular static members?

不。课程根本不是线程安全的。单例与它无关。

(I'm getting my head around the fact that instance members called on a static object cause threading problems)

这也没有关系。

我们必须这样考虑:在我的程序中是否可以有2个(或者更多)线程同时访问此数据?

通过单例或者静态变量获取数据,或者将对象作为方法参数传入都无所谓。归根结底,这只是PC RAM中的一些位和字节,而重要的是多个线程是否可以看到相同的位。

回答

I was thinking that if I dump the singleton pattern and force everyone to get a new instance of the class it would ease some problems... but that doesn't stop anyone else from initializing a static object of that type and passing that around... or from spinning off multiple threads, all accessing 'Toggle()' from the same instance.

答对了 :-)

I get it now. It's a tough world. I wish I weren't refactoring legacy code :(

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