静态类的奇怪行为

时间:2014-06-08 10:22:47

标签: 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 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类是静态的吗?

2 个答案:

答案 0 :(得分:6)

是的,你这里有竞争条件;不,这不是因为班级是static

问题是在LockedByReason1 = falseLocker.Locked的读取之间(它本身远,与原子操作相比),其他线程可能会执行使您的代码锁定条件为真。

一般来说,这种设计根本不提供对竞争条件的任何保护,因为它既不使用原子操作也不使用同步原语(例如lock语句)。

答案 1 :(得分:0)

你没有任何竞争条件,因为没有种族。这只是顺序单线程操作。您将所有LockedByReason值设置为false(bool字段为false加默认值,您还将LockedByReason1设置为false)。在条件语句中,检查Locked值,这给出了第一个日志记录。结果是错误的,这导致第二个,#34; Not unlocking system"日志记录。日志记录的相同时间是因为输出比DateTime精度快得多。

但是你的代码不是线程安全的。所以有点优化的版本,没有使用昂贵的"锁定"指令可能看起来像:

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;
        }
    }
}