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了