该时间戳方法如何返回重复值

时间:2014-05-20 16:11:11

标签: c# unique

我有一个方法应该在Base36中生成一个独特的10个字符的时间戳,具有微秒分辨率。然而,它是唯一性测试失败。怎么会这样?

    private static string _lastValue = string.Empty;
    private static readonly DateTime _epoch = DateTime.SpecifyKind(new DateTime(1970,1,1), DateTimeKind.Utc);
    private static readonly DateTime _lastInitialized = DateTime.Now; 
    private static readonly Stopwatch _sw = Stopwatch.StartNew();

    public static TimeSpan EpochToStopwatchStart()
    {
        return _lastInitialized.Subtract(_epoch);
    }

    public static string GetBase36Timestamp()
    {
        string result;
        do
        {
            // _sw is a running Stopwatch; Microseconds = ticks / 10
            long microseconds = EpochToStopwatchStart().Add(_sw.Elapsed).Ticks / 10L;
            result = MicrosecondsToBase36(microseconds);
        }
        // MicrosecondsToBase36 encodes the Int64 value; the while() loop compares to a
        // tracking field to ensure the encoded value changes from the previous one:
        while (result == _lastValue);
        _lastValue = result;
        return result;
    }

我知道我放弃了一些分辨率,但这需要Base36中的10个字符,并且该方法无论如何都会检查编码值。在一次运行中发生意外的欺骗。为了简化问题,我运行单线程测试。我希望答案非常有趣,否则我会对这个问题中的一些非常愚蠢的疏忽感到尴尬。

3 个答案:

答案 0 :(得分:1)

如果在do / while循环中添加Thread.Sleep(1);会怎样?你很可能每次迭代产生的时间超过一微秒。

答案 1 :(得分:1)

<强>分析

创建多线程性能测试表明,尽管有while循环,但该函数能够以每微秒一次以上的速率退出:

    static void Main(string[] args)
    {
        List<string> timeStamps = null; ;

        int calls = 1000000;
        int maxThreads = 5;

        for (int threadCount = 1; threadCount <= maxThreads; threadCount++)
        {
            timeStamps = new List<string>(calls * maxThreads);

            var userThread = new ThreadStart(() =>
                {
                    for (int n = 0; n < calls; n++)
                    {
                        timeStamps.Add(TimeStampClass.GetBase36Timestamp());
                    }
                });

            Thread[] threads = new Thread[threadCount];
            var stopwatch = Stopwatch.StartNew();

            for (int j = 0; j < threadCount; j++)
            {
                threads[j] = new Thread(userThread);
                threads[j].Start();
            }

            for (int j = 0; j < threadCount; j++)
            {
                threads[j].Join();
            }

            stopwatch.Stop();

            Console.WriteLine("threadCount = {0}\n ------------------", threadCount);

            Console.WriteLine("{0} calls in {1} milliseconds", timeStamps.Count, stopwatch.ElapsedMilliseconds);
            Console.WriteLine("{0} ticks per call", (double)stopwatch.Elapsed.Ticks / (double)timeStamps.Count);

            Console.WriteLine();
        }

结果输出为:

threadCount = 1
 ------------------
1000000 calls in 1080 milliseconds
10.802299 ticks per call

threadCount = 2
 ------------------
1985807 calls in 1379 milliseconds
6.94705779564681 ticks per call

threadCount = 3
 ------------------
2893411 calls in 1731 milliseconds
5.98568471606695 ticks per call

threadCount = 4
 ------------------
3715722 calls in 2096 milliseconds
5.64319478152564 ticks per call

threadCount = 5
 ------------------
4611970 calls in 2395 milliseconds
5.19515413153164 ticks per call

多线程环境解决方案:

锁定while上的_lastValue循环:

        public static string GetBase36Timestamp()
        {
            string result;
            lock (_lastValue)
            {
                do
                {
                    // _sw is a running Stopwatch; Microseconds = ticks / 10
                    long microseconds = EpochToStopwatchStart().Add(_sw.Elapsed).Ticks / 10L;
                    result = MicrosecondsToBase36(microseconds);
                } while (result == _lastValue);
            }
            return result;
        }

答案 2 :(得分:1)

我认为你需要使用System.Threading.Interlocked.CompareExchange()来做一个线程安全的比较和交换作为原子操作。有关详细信息,请参阅Interlocked Operations。简而言之,你......

  • 获取要更改为本地变量的状态的副本。
  • 执行计算以获得新状态
  • 执行Interlocked.CompareExchange(),返回当前的旧值
  • 如果旧值的本地副本与返回值不同,则交换失败:重复上述操作。
  • 否则,你很高兴。

这是一个简化的例子,重复你的工作:

class TimeStamp
{
  static readonly DateTime  unixEpoch        = new DateTime(1970,1,1,0,0,0,DateTimeKind.Utc) ;
  static readonly long      BaseMicroseconds = (DateTime.UtcNow-unixEpoch).Ticks / 10L ;
  static readonly Stopwatch Stopwatch        = Stopwatch.StartNew() ;
  static          long      State            = TimeSpan.MinValue.Ticks ;

  private long OffsetInMicroseconds ;

  private TimeStamp()
  {
    long oldState ;
    long newState ;

    do
    {
      oldState = State ;
      newState = Stopwatch.Elapsed.Ticks / 10L ;
    } while (    oldState == newState
              || oldState != Interlocked.CompareExchange( ref State , newState , oldState )
            ) ;

    this.OffsetInMicroseconds = newState ;
    return ;
  }

  public static TimeStamp GetNext()
  {
    return new TimeStamp() ;
  }

  public override string ToString()
  {
    long   v = BaseMicroseconds + this.OffsetInMicroseconds ;
    string s = v.ToString() ; // conversion to Base 36 not implemented ;
    return s ;
  }

}