(无效的方法)避免 C# 中的双重检查锁
这是避免双重检查锁的有效且优化的方法吗:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
System.Threading.Thread.MemoryBarrier();
lock (InitializeLock)
{
// do init stuff
IsInitialized = true;
}
}
//Do stuff that have to happen when function is called
}
}
这是双重检查的替代方案:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
lock (InitializeLock)
{
if (!IsInitialized)
{
// do init stuff
IsInitialized = true;
}
}
}
//Do stuff that have to happen when function is called
}
}
Is this a valid and optimized way to avoid double checked locks:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
System.Threading.Thread.MemoryBarrier();
lock (InitializeLock)
{
// do init stuff
IsInitialized = true;
}
}
//Do stuff that have to happen when function is called
}
}
With this being the double-checked alternative:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
lock (InitializeLock)
{
if (!IsInitialized)
{
// do init stuff
IsInitialized = true;
}
}
}
//Do stuff that have to happen when function is called
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
不,因为线程切换可能在两个线程通过
if (!IsInitialized)
之后立即发生。有一篇很棒的文章在创建单例的上下文中解释了此主题:http://csharpindepth.com/Articles/General/Singleton.aspx(作者:Jon Skeet)
No, because thread switch can happen right after two threads pass
if (!IsInitialized)
There is a great article where this topic is explained in context of creating singleton: http://csharpindepth.com/Articles/General/Singleton.aspx (by Jon Skeet)
今天这个问题已经是第二次出现了。请参阅:
C# 手动锁定/解锁
您的问题的简短答案是否定的,那是绝对无效的。如果“IsInitialized”的非易失性读取相对于正在初始化的任何状态的非易失性读取进行了重新排序,则代码路径上永远不会有任何类型的内存屏障,因此读取可以重新排序,因此“IsInitialized”可以为 true,同时过时的缓存未初始化状态仍然良好。
你必须做的是(1)不进行双重检查锁定;这是危险的,或者(2)确保始终至少有一次 IsInitialized 的易失性读取,以防止对初始化状态的读取及时向后移动。
This is the second time this question has come up today. See:
C# manual lock/unlock
The short answer to your question is no, that is absolutely not valid. If the non-volatile read of "IsInitialized" is reordered with respect to the non-volatile read of whatever state is being initialized then the code path never has a memory barrier on it of any sort, and therefore the reads can be re-ordered, and therefore "IsInitialized" can be true while the out-of-date cached uninitialized state is still good.
What you have to do is either (1) don't do double-checked locking; it is dangerous, or (2) ensure that there is always at least one volatile read of IsInitialized to prevent reads of the initialized state being moved backwards in time.
第一个示例中的
MemoryBarrier
调用是完全多余的,因为后续的lock
调用无论如何都会创建隐式内存屏障。即使您在第一次
IsInitialized
检查之前移动了内存屏障,代码仍然不安全:在IsInitialized
检查和检查之间有一个线程可以中断的窗口>lock
语句。这就是为什么您通常需要在lock
块内进行第二次IsInitialized
检查。The
MemoryBarrier
call in your first example is completely superfluous since the subsequentlock
call creates an implicit memory barrier anyway.Even if you moved the memory barrier before the first
IsInitialized
check, the code is still unsafe: there's a window for the thread to be interrupted between theIsInitialized
check and thelock
statement. That's why you generally need a secondIsInitialized
check inside thelock
block.您可以通过将
IsInitialized
标志设置为易失性
来帮助检查,这将阻止其他线程缓存它(这是一个非常小的改进,因为您正在锁定),但锁定后您仍然需要该标志。换句话说,除非使用一些棘手的初始化,否则无法避免双重检查锁。但是,如果您重新设计类并且采用乐观的方法来更改状态,则可以取消锁......这应该像魅力一样起作用:
You can help the check by making the
IsInitialized
flagvolatile
which will prevent other threads from caching it (a very minor improvement since you're locking), but you still need the flag after you're locking. In other words, you can't avoid the double-checked lock unless you use some tricky initialization.However, you can do away with the locks if you re-design your class and if you go to an optimistic approach of changing the state... this should work like a charm: