锁定资源并根据锁定时间生成时间戳

Locking a Resource and generating Time Stamps according to lock time

假设我想实现一个同步原语,它生成一个将在同步协议中使用的时间戳。时间戳是这样的,对于用于锁定 资源 的给定 key,没有两个线程能够获得相同的时间戳值。

后一个规范的可能实现是:

namespace InfinityLabs.PowersInfinity.BCL.Synchronization
{

public static class TimeStampMonitor
{
    private static readonly IDictionary<object, long> TimeStamps;

    static TimeStampMonitor()
    {
        TimeStamps = new Dictionary<object, long>();
    }

    #region API

    public static long Enter(object key)
    {
        var lockTaken = false;

        Monitor.Enter(key, ref lockTaken);

        ThrowIfLockNotAcquired(key, lockTaken);

        var timeStamp = GetCurrentTimeStamp();

        Thread.Sleep(1);

        TimeStamps.Add(key, timeStamp);

        return timeStamp;
    }

    public static void Exit(object key)
    {
        var lockTaken = false;

        Monitor.Enter(key, ref lockTaken);

        try
        {
            ThrowIfInvalidKey(key);
            TimeStamps.Remove(key);
        }
        finally
        {
            if (lockTaken)
                Monitor.Exit(key);
        }
    }

    public static long GetTimeStampOrThrow(object key)
    {
        TryEnterOrThrow(key);

        var timeStamp = GetTimeStamp(key);
        return timeStamp;
    }

    public static void TryEnterOrThrow(object key)
    {
        var lockTaken = false;

        try
        {
            Monitor.Enter(key, ref lockTaken);

            ThrowIfLockNotAcquired(key, lockTaken);
            ThrowIfInvalidKey(key);
        }
        catch (SynchronizationException)
        {
            throw;
        }
        catch (Exception)
        {
            if(lockTaken)
                Monitor.Exit(key);

            throw;
        }
    }

    #endregion

    #region Time Stamping

    private static long GetCurrentTimeStamp()
    {
        var timeStamp = DateTime.Now.ToUnixTime();
        return timeStamp;
    }

    private static long GetTimeStamp(object key)
    {
        var timeStamp = TimeStamps[key];
        return timeStamp;
    }

    #endregion

    #region Validation

    private static void ThrowIfInvalidKey(object key, [CallerMemberName] string methodName = null)
    {
        if (!TimeStamps.ContainsKey(key))
            throw new InvalidOperationException($"Must invoke '{nameof(Enter)}' prior to invoking '{methodName}'. Key: '{key}'");
    }

    private static void ThrowIfLockNotAcquired(object key, bool lockTaken)
    {
        if (!lockTaken)
            throw new SynchronizationException($"Unable to acquire lock for key '{key}'");
    }
    #endregion
}
}

注意这两个 API 方法 TryEnterOrThrowGetTimeStampOrThrow 旨在通过消费 类作为保护方法,它不允许编写不当的代码破坏临界区的原子性。后一种方法还 returns 先前为给定密钥获取的时间戳值。时间戳保留的时间很长,以至于它的所有者没有退出临界区。

我已经 运行 想过所有可能的场景,但我似乎无法打破它——不仅是原子地,而且是试图滥用它。 我想我的问题是,因为这是我为数不多的编写同步原语的尝试之一——这段代码是万无一失的吗,它是否提供原子性?

不胜感激!

看看

您可以在不锁定的情况下创建唯一的时间戳。下面的代码。

您的问题略有不同,因为您希望每个键都有一个唯一的时间戳,但是如果您有一个全局唯一的时间戳,那么您会自动为每个键分配一个唯一的时间戳,而无需任何额外的努力。所以我认为你真的不需要字典:

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long original, newValue;
           do
           {
               original = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newValue = Math.Max(now, original + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newValue, original) != original);

           return newValue;
       }
   }
}