Как обеспечить, чтобы временная метка всегда была уникальной? - PullRequest
29 голосов
/ 10 апреля 2011

Я использую временные метки для временного упорядочения параллельных изменений в моей программе и требую, чтобы каждая временная метка изменения была уникальной.Однако я обнаружил, что простого вызова DateTime.Now недостаточно, поскольку он часто возвращает одно и то же значение, если вызывается в быстрой последовательности.

У меня есть некоторые мысли, но ничто не кажется мне "лучшим" решениемк этому.Могу ли я написать метод, который будет гарантировать, что каждый последующий вызов выдаст уникальный DateTime?

Должен ли я использовать для этого другой тип, например long int?DateTime имеет очевидное преимущество, заключающееся в том, что его легко интерпретировать как реальное время, в отличие, скажем, от счетчика приращений.

Обновление: Вот что я закончил кодировать как простое компромиссное решение, котороевсе еще позволяет мне использовать DateTime в качестве моего временного ключа, обеспечивая при этом уникальность при каждом вызове метода:

private static long _lastTime; // records the 64-bit tick value of the last time
private static object _timeLock = new object();

internal static DateTime GetCurrentTime() {
    lock ( _timeLock ) { // prevent concurrent access to ensure uniqueness
        DateTime result = DateTime.UtcNow;
        if ( result.Ticks <= _lastTime )
            result = new DateTime( _lastTime + 1 );
        _lastTime = result.Ticks;
        return result;
    }
}

Поскольку каждое значение тика составляет только одну 10-миллионную долю секунды, этот метод вводит толькозаметный перекос часов при вызове порядка 10 миллионов раз в секунду (что, кстати, достаточно эффективно для выполнения), что означает, что он вполне приемлем для моих целей.

Вот некоторый тестовый код:

DateTime start = DateTime.UtcNow;
DateTime prev = Kernel.GetCurrentTime();
Debug.WriteLine( "Start time : " + start.TimeOfDay );
Debug.WriteLine( "Start value: " + prev.TimeOfDay );
for ( int i = 0; i < 10000000; i++ ) {
    var now = Kernel.GetCurrentTime();
    Debug.Assert( now > prev ); // no failures here!
    prev = now;
}
DateTime end = DateTime.UtcNow;
Debug.WriteLine( "End time:    " + end.TimeOfDay );
Debug.WriteLine( "End value:   " + prev.TimeOfDay );
Debug.WriteLine( "Skew:        " + ( prev - end ) );
Debug.WriteLine( "GetCurrentTime test completed in: " + ( end - start ) );

... и результаты:

Start time:  15:44:07.3405024
Start value: 15:44:07.3405024
End time:    15:44:07.8355307
End value:   15:44:08.3417124
Skew:        00:00:00.5061817
GetCurrentTime test completed in: 00:00:00.4950283

Другими словами, за полсекунды он сгенерировал 10 миллионов уникальных временных меток и окончательнуюрезультат опередил только на полсекунды.В реальных приложениях перекос был бы незаметен.

Ответы [ 6 ]

68 голосов
/ 17 января 2013

Одним из способов получения строго возрастающей последовательности временных меток без дубликатов является следующий код.

По сравнению с другими ответами здесь у этого есть следующие преимущества:

  1. Значения тесно связаны с фактическими значениями в реальном времени (за исключением экстремальных обстоятельств с очень высокими показателями запросов, когда онинемного опередил бы в реальном временинет).

.

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;
       }
   }
}
7 голосов
/ 10 апреля 2011

Э-э, ответ на ваш вопрос заключается в том, что «вы не можете», поскольку, если две операции происходят одновременно (что происходит в многоядерных процессорах), они будут иметь одну и ту же временную метку независимо от точности вам удастся собрать.

Тем не менее, звучит так, как будто вы хотите что-то вроде автоинкрементного потокобезопасного счетчика. Чтобы реализовать это (предположительно как глобальный сервис, возможно, в статическом классе), вы должны использовать метод Interlocked.Increment, и если вы решите, что вам нужно более int.MaxValue возможных версий, также Interlocked.Read.

6 голосов
/ 10 апреля 2011

DateTime.Now обновляется только каждые 10-15 мс.

Сам по себе не дурак, но у этого потока есть некоторые идеи по сокращению дубликатов / обеспечению лучшего разрешения синхронизации:

Как получить временную метку точности тиков в .NET / C #?

При этом: метки времени - ужасные ключи для информации;если вещи происходят так быстро, вы можете захотеть индекс / счетчик, который сохраняет дискретный порядок элементов по мере их появления.Там нет никакой двусмысленности.

3 голосов
/ 10 апреля 2011

Я считаю, что самый надежный способ - объединить метку времени и атомный счетчик. Вы уже знаете проблему с плохим разрешением отметки времени. Использование атомного счетчика само по себе также имеет простую проблему - требовать сохранения его состояния, если вы собираетесь остановить и запустить приложение (в противном случае счетчик возвращается в 0, вызывая дублирование).

Если бы вы просто использовали уникальный идентификатор, это было бы так же просто, как объединить метку времени и значение счетчика с разделителем между ними. Но поскольку вы хотите, чтобы значения всегда были в порядке, этого будет недостаточно. По сути, все, что вам нужно сделать, это использовать значение атомного счетчика, чтобы добавить дополнительную фиксированную точность ширины к вашей временной метке. Я являюсь разработчиком Java, поэтому пока не смогу предоставить пример кода на C #, но проблема одинакова в обеих областях. Так что просто следуйте этим общим шагам:

  1. Вам потребуется метод, который предоставит вам значения счетчиков, циклически изменяющиеся от 0-99999. 100000 - это максимальное число возможных значений при объединении метки времени с точностью до миллисекунды с фиксированным значением ширины длиной 64 бита. Таким образом, вы в основном предполагаете, что вам никогда не понадобится более 100000 идентификаторов в одном временном разрешении (15 мс или около того). Статический метод, использующий класс Interlocked для обеспечения атомарного приращения и сброса в 0, является идеальным способом.
  2. Теперь, чтобы сгенерировать свой идентификатор, вы просто объединяете свою временную метку со значением счетчика, дополненным до 5 символов. Поэтому, если ваша временная метка была 13023991070123, а ваш счетчик 234, идентификатор будет 1302399107012300234.

Эта стратегия будет работать до тех пор, пока вам не нужны идентификаторы быстрее, чем 6666 в мс (при условии, что 15 мс - ваше наиболее детальное разрешение), и всегда будет работать без сохранения какого-либо состояния при перезапусках вашего приложения.

1 голос
/ 10 апреля 2011

Это не может быть гарантированно уникальным, но, возможно, использование тиков достаточно гранулировано?

Один тик представляет сто наносекунд или одну десятую миллионнуюВторой.В миллисекунде 10 000 тиков.

0 голосов
/ 10 апреля 2011

Не уверен, что вы пытаетесь сделать полностью, но, возможно, посмотрите, как использовать очереди для последовательной обработки записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...