【问题标题】:Locking a Resource and generating Time Stamps according to lock time锁定资源并根据锁定时间生成时间戳
【发布时间】:2015-10-24 20:02:22
【问题描述】:

假设我想实现一个同步原语,它生成一个将在同步协议中使用的时间戳。时间戳是这样的,对于用于锁定 资源 的给定 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
}
}

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

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

我们将不胜感激!

【问题讨论】:

  • 只看这个是行不通的:在锁下睡觉通常是超慢的
  • 这里有一个问题...DateTime.Now 仅在几毫秒的间隔内更新,因为这不是像 TimeSpan 那样的高精度实现。
  • @Lol4t0 : 你会推荐什么?
  • @AndreasNiedermair 我会考虑的!谢谢!
  • 这实际上取决于任务。可能只是一个用 0 初始化的整数,每次调用时递增 1 就可以了(所以它会像 logical 时间戳一样工作)。但我必须了解您首先要实现的目标(而且很可能无论如何我都无法在 C# 领域提供最佳答案)

标签: c# multithreading synchronization critical-section trusted-timestamp


【解决方案1】:

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

【讨论】:

  • 我必须锁定,因为我在同步协议中使用时间戳,每个键必须阻止尝试使用资源的线程..
  • @EyalPerry 请阅读Interlocked 上的文档。只有一个线程可以比较和交换值,因此通过强制 CPU 指令排序是无锁的。
  • 顺便说一句:DateTime.Now 不是高精度定时的,因为它的内部值仅每 35 毫秒左右更新一次。
  • @AndreasNiedermair 注意到了,先生!
猜你喜欢
  • 1970-01-01
  • 2023-01-11
  • 2017-06-15
  • 1970-01-01
  • 2015-12-05
  • 2019-01-04
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
相关资源
最近更新 更多