C# 静态类的奇怪行为
我有一个可以通过多种原因锁定的系统。C# 静态类的奇怪行为,c#,static,race-condition,C#,Static,Race Condition,我有一个可以通过多种原因锁定的系统。 下面是负责保持锁定状态的静态类: internal static class Locker { private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); internal static bool LockedByReason1 { get; set; } internal static bool Locked
下面是负责保持锁定状态的静态类:
internal static class Locker
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
internal static bool LockedByReason1 { get; set; }
internal static bool LockedByReason2 { get; set; }
internal static bool LockedByReason3 { get; set; }
internal static bool Locked
{
get
{
log.DebugFormat("LockedByReason1: {0}, LockedByReason2: {1}, LockedByReason3: {2}", LockedByReason1, LockedByReason2, LockedByReason3);
return LockedByReason1 || LockedByReason2 || LockedByReason3;
}
}
}
这是业务逻辑中的代码:
Locker.LockedByReason1 = false;
if (Locker.Locked)
log.Info("Unlocking system...");
else
log.Info("Not unlocking system");
我的日志文件显示以下文本:
2014-06-06 10:54:31,765 DEBUG Client.Utils.Locker - LockedByReason1: False, LockedByReason2: False, LockedByReason3: False
2014-06-06 10:54:31,765 INFO Client.BusinessLogicManager - Not unlocking system
如您所见,同时调用了LockedByReason1
属性的设置和查询Locked
状态。我这里有比赛条件问题吗?
是因为
Locker
类是静态的吗?是的,这里有一个竞赛条件;不,这不是因为类是静态的
问题在于,在LockedByReason1=false
和读取Locker.Locked
(它本身远离原子操作)之间,其他线程可能会执行使锁定条件为真的代码
一般来说,这种设计根本不提供竞争条件下的任何保护,因为它既不使用原子操作也不使用同步原语(例如
lock
语句)。是的,这里有竞争条件;不,这不是因为类是静态的
问题在于,在LockedByReason1=false
和读取Locker.Locked
(它本身远离原子操作)之间,其他线程可能会执行使锁定条件为真的代码
一般来说,这种设计根本不提供任何竞争条件的保护,因为它既不使用原子操作也不使用同步原语(例如
lock
语句)。这里没有任何竞争条件,因为没有竞争。这只是连续的单线程操作。您已经将所有LockedByReason值设置为false(bool字段为false加上默认值,并且还将LockedByReason 1设置为false)。在条件语句中,检查锁定的值,这将给出第一条日志记录。结果为假,这将导致第二条“未解锁系统”日志记录。日志中的时间记录是相同的,因为输出比日期时间精度快得多
但是你的代码不是线程安全的。因此,一个稍微优化的版本,不使用昂贵的“lock”指令,看起来可能是:
internal static class Locker
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// We join or lockers into one field, because boolean cann't be handled by Interlocked routines.
private static volatile int _Lockers = 0;
// Thread-safe update routine
internal static void AtomicUpdate(int mask, bool value)
{
SpinWait sw = new SpinWait();
do
{
int old = _Lockers;
if (Interlocked.CompareExchange(ref _Lockers, value ? old | mask : old &~mask, old) == old)
{
return;
}
sw.SpinOnce();
} while (true);
}
// Reason 1 will be the first bit of _Lockers field, Reason 2 - the second and so on.
internal static bool LockedByReason1
{
get
{
return (_Lockers & 1) > 0;
}
set
{
Locker.AtomicUpdate(1, value);
}
}
internal static bool LockedByReason2
{
get
{
return (_Lockers & 2) > 0;
}
set
{
Locker.AtomicUpdate(2, value);
}
}
internal static bool LockedByReason3
{
get
{
return (_Lockers & 4) > 0;
}
set
{
Locker.AtomicUpdate(4, value);
}
}
internal static bool Locked
{
get
{
log.DebugFormat("LockedByReason1: {0}, LockedByReason2: {1}, LockedByReason3: {2}", LockedByReason1, LockedByReason2, LockedByReason3);
return _Lockers > 0;
}
}
}
这里没有任何比赛条件,因为没有比赛。这只是连续的单线程操作。您已经将所有LockedByReason值设置为false(bool字段为false加上默认值,并且还将LockedByReason 1设置为false)。在条件语句中,检查锁定的值,这将给出第一条日志记录。结果为假,这将导致第二条“未解锁系统”日志记录。日志中的时间记录是相同的,因为输出比日期时间精度快得多 但是你的代码不是线程安全的。因此,一个稍微优化的版本,不使用昂贵的“lock”指令,看起来可能是:
internal static class Locker
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// We join or lockers into one field, because boolean cann't be handled by Interlocked routines.
private static volatile int _Lockers = 0;
// Thread-safe update routine
internal static void AtomicUpdate(int mask, bool value)
{
SpinWait sw = new SpinWait();
do
{
int old = _Lockers;
if (Interlocked.CompareExchange(ref _Lockers, value ? old | mask : old &~mask, old) == old)
{
return;
}
sw.SpinOnce();
} while (true);
}
// Reason 1 will be the first bit of _Lockers field, Reason 2 - the second and so on.
internal static bool LockedByReason1
{
get
{
return (_Lockers & 1) > 0;
}
set
{
Locker.AtomicUpdate(1, value);
}
}
internal static bool LockedByReason2
{
get
{
return (_Lockers & 2) > 0;
}
set
{
Locker.AtomicUpdate(2, value);
}
}
internal static bool LockedByReason3
{
get
{
return (_Lockers & 4) > 0;
}
set
{
Locker.AtomicUpdate(4, value);
}
}
internal static bool Locked
{
get
{
log.DebugFormat("LockedByReason1: {0}, LockedByReason2: {1}, LockedByReason3: {2}", LockedByReason1, LockedByReason2, LockedByReason3);
return _Lockers > 0;
}
}
}
我认为这一切都能正常工作:您正在将
锁定Byreason1
设置为False
,打印输出显示它是False
,所有三项都是False
,因此它们的“OR”也是False
,因此if
在False
上进行分支。你认为会发生什么不同?我不会对时间戳读太多。在调用日志系统之间,系统时钟可能没有更新。通常每15毫秒更新一次。记录这两个项目之间的时间大约为10纳秒。@dasblinkenlight-我认为这是一个锁或线程问题,但显然有时你不得不抬起头离开键盘,深呼吸,意识到这只是一个丢失的感叹号。谢谢。我认为这一切都正常工作:您正在将锁定Byreason1
设置为False
,打印输出显示为False
,所有三项都是False
,因此它们的“OR”也是False
,因此if
将分支转到False
。你认为会发生什么不同?我不会对时间戳读太多。在调用日志系统之间,系统时钟可能没有更新。通常每15毫秒更新一次。记录这两个项目之间的时间大约为10纳秒。@dasblinkenlight-我认为这是一个锁或线程问题,但显然有时你不得不抬起头离开键盘,深呼吸,意识到这只是一个丢失的感叹号。谢谢。你确定他在储物柜前并没有错过一个感叹号。他被锁在if
状态下?@dasblinkenlight:是的,但有一个明显的比赛状态,这就是问题所在…你确定他在储物柜前没有错过一个感叹号。在他的if
状态中锁定了?@dasblinkenlight:是的,但有一个明显的种族条件,这就是问题所在…我认为这在技术上并不安全。尽管Interlocked.Exchange
调用本身是线程安全的,但我不认为它的值的收集,特别是\u Lockers | 2
之类的表达式是线程安全的。当\u Lockers\uu
为0
时,LockByReason1
和LockByReason2
设置器同时被调用,这似乎是合理的,它们同时读取\u Lockers
字段,并将它们的表达式计算为:0 | 1
和0 | 2
,最后发生的Exchange
调用将赢得抛出旧值的机会。您肯定是对的。代码中还有一些错误。已经更新了。神圣的烟。根据我的经验,在纠正多线程代码时,在私有锁定对象周围使用标准的lock
几乎总是更好的。只有在标准锁产生真正可测量的影响后,才开始优化线程方面。看一眼你现在的代码,我就说不出t了