0

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

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

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

请注意,TryEnterOrThrowGetTimeStampOrThrow这两个 API 方法旨在通过使用类作为保护方法来使用,这些方法不允许编写不佳的代码破坏临界区的原子性。后一种方法还返回给定键的先前获取的时间戳值。时间戳保持如此之久,以至于其所有者没有退出关键部分。

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

帮助将不胜感激!

4

1 回答 1

2

看看https://stackoverflow.com/a/14369695/224370

您可以创建一个唯一的时间戳而无需锁定。代码如下。

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

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;
       }
   }
}
于 2015-10-24T20:08:34.450 回答