单例成员的线程安全使用
我有一个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 :(
不幸的是,多线程很难,并且我们必须对事情非常偏执:-)
在这种情况下,最简单的解决方案是坚持单例,并在值周围添加一个锁,如示例中所示。