我在多线程环境中有一个高吞吐量的Singleton。通常我会做这样的事情:
public static Foo GetInstance()
{
lock (Foo._syncLock)
{
if (Foo._instance == null)
Foo._instance = new Foo();
return Foo._instance;
}
}
我想知道如果执行以下操作会更有效率,因为它可以避免连续的线程锁定,或者是否存在隐藏的问题?
public static Foo GetInstance()
{
if (Foo._instance != null)
return Foo._instance;
lock (Foo._syncLock)
{
if (Foo._instance == null)
Foo._instance = new Foo();
return Foo._instance;
}
}
答案 0 :(得分:9)
我想知道如果执行以下操作会更有效率,因为它可以避免连续的线程锁定,或者是否存在隐藏的问题?
你的问题是“通过采用危险的低锁模式会有性能提升吗?”这是要问的完全错误的问题。永远不要这样说!这种方式浪费时间,浪费精力,以及疯狂,不可能调试的错误。
正确的问题是“我的测量结果是否强烈表明我的性能问题一开始?”。
如果答案是“否”,那么你就完成了。
只有当答案为“是”时,您才会问下一个问题,即“我可以通过消除对锁的争用来消除我的性能问题吗?”
如果答案为“是”,则消除对锁定的争用并返回第一个问题。
如果答案是“否”,那么你应该问下一个问题,即“将采用低锁定解决方案提供可接受的性能?”
请注意,对于此问题的答案为“是”,您必须处于无竞争锁定施加的十纳秒惩罚是您的表现的门控因素的情况。 很少有人处于10或20纳秒太长的位置。
在极不可能的事件中,答案是“是”,你应该继续下一个问题,即“双重检查锁定的正确实现是否会消除我的性能问题?”
如果双重检查锁定不够快,那么实现它是一个非启动器。你必须以其他方式解决你的问题。
只有当问题的答案为“是”时,才应实施双重检查锁定。
现在让我们来看看你的实际问题:
它有隐藏的问题吗?
您的实施是正确的。然而,当你偏离幸福的模式时,所有的赌注都会被取消。例如:
static object sync = new object();
static bool b = false;
static int x = 0;
static int GetIt()
{
if (!b)
{
lock(sync)
{
if (!b)
{
b = true;
x = ExpensiveComputation();
}
}
}
return x;
}
看起来没错,对吗?但这不正确!考虑低锁路径。由于此路径上没有障碍,x的值可以在一个线程上预取为零,然后另一个线程可以运行并将b设置为true,将x设置为123,然后原始线程可以获取b,get为true,并返回预先获取的x。
那么解决方案是什么?根据我的偏好顺序,他们是:
Lazy<T>
。InterlockedCompareExchange
。 答案 1 :(得分:0)
避免锁定总是一个好主意!但......正常的方式是:
原始代码:
if(_instance == null)
lock(_syncRoot)
if(_instance == null)
_instance = new Foo();
return _instance;
通过这种方式,您可以在锁定之前和之后进行验证,以确保不会引入竞争条件。
这个想法是在锁释放后,一个单独的线程可能已经初始化了该对象。所以你应该再次检查。
<强>说明强>
如果您对此主题有足够的了解,您会发现大多数人都反对 Singleton
方式使用parallel
结构。
修改强>
根据评论:更好的方法是允许框架实例化静态成员。如果我理解正确,这将消除对任何locking
的需求。
private static Foo _instance = new Foo();
然而,正如Eric指出的那样......如果很少有线程一次访问对象,则不需要避免锁定。