Почему нет потокобезопасной альтернативы С++ 11 для std::localtime и std::gmtime?

В С++ 11 вам все равно придется использовать std::localtime и std::gmtime как косвенное указание, чтобы напечатать std::chrono::time_point. Эти функции небезопасно использовать в многопоточной среде, представленной в C++11, поскольку они возвращают указатель на внутреннюю статическую структуру. Это особенно раздражает, поскольку C++11 представил удобную функцию std::put_time, которая почти непригодна для использования по той же причине.

Почему это так фундаментально нарушено или я что-то упускаю из виду?


person TNA    schedule 02.09.2014    source источник
comment
Вау, эти функции почти так же хорошо разработаны, как strtok.   -  person CodesInChaos    schedule 02.09.2014
comment
Программирование на С++ не должно быть легким. Я полагаю, что это по-прежнему основная философия - вам нужно что-то, что вы пишете сами, если только это не является абсолютным минимумом голых костей.   -  person zzz777    schedule 24.03.2015
comment
Я полностью согласен с вашим разглагольствованием. Было бы легко реализовать std::localtime() как оболочку для localtime_r() GLIBC. Но теперь он сломан в стандарте.   -  person Kai Petzke    schedule 14.12.2018


Ответы (6)


Согласно N2661, статья, в которой добавлено <chrono>:

Эта статья не предлагает календарных услуг, за исключением минимального сопоставления с time_t языка C и обратно.

Поскольку в этом документе не предлагается библиотека даты/времени и не указываются эпохи, в нем также не рассматриваются дополнительные секунды. Однако библиотека даты/времени сочтет это отличным фундаментом, на котором можно строить.

В этой статье не предлагается библиотека физических величин общего назначения.

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

Основная цель этого предложения — удовлетворить потребности API потоковой передачи стандартной библиотеки таким образом, чтобы он был простым в использовании, безопасным в использовании, эффективным и достаточно гибким, чтобы не устаревать через 10 или даже 100 лет. Каждая функция, содержащаяся в этом предложении, здесь по определенной причине с практическими вариантами использования в качестве мотивации. Вещи, которые попали в категорию «круто», или «звучит так, будто это может быть полезно», или «очень полезно, но не нужно для этого интерфейса», не были включены. Такие пункты могут появиться в других предложениях и, возможно, предназначаться для ТР.

Обратите внимание, что основная цель <chrono> — «удовлетворить потребности API потоковой передачи стандартной библиотеки», для которого не требуются службы календаря.

person T.C.    schedule 02.09.2014
comment
Хорошо, это может объяснить выбор дизайна, кроме введения std::put_time без сохранения, независимого от платформы способа его использования. - person TNA; 02.09.2014
comment
@TNA Ну, это было задумано как замена для strftime, для которого также требуется tm *. - person T.C.; 02.09.2014
comment
На мой взгляд, если вы замените C-функцию функцией C++, она должна быть последовательной, чтобы вам не нужны были небезопасные C-функции для ее использования. Не существует сохранения, независимого от платформы способа получить tm, поэтому все функции, использующие tm, неприменимы в C++11, за исключением некоторых особых случаев. Там нет стандартного способа обойти это. - person TNA; 02.09.2014
comment
Настоящая причина в том, что все, что касалось времени, вызывало невероятные споры в комитете, и было бы жалко просто <chrono> стандартизировать. Если бы мы также попытались использовать календарные сервисы, C++11 был бы назван как минимум C++14. Здесь home.roadrunner.com/~hinnant/bloomington/date.html — календарное предложение после C++11, которое комитет ненавидел. - person Howard Hinnant; 02.09.2014
comment
@HowardHinnant Я могу понять, что не поддерживает сложные операции, связанные с календарем. Но это не объясняет, почему он возвращает ссылку на глобальную изменяемую переменную вместо того, чтобы принимать ее как параметр по ссылке или возвращать по значению. - person CodesInChaos; 02.09.2014
comment
@CodesInChaos: потому что API localtime/gmtime старше битов. ;-) - person Howard Hinnant; 02.09.2014
comment
Ссылка, которую я дал выше, на календарное предложение, которое ненавидел комитет, умерла. Вот свежая версия этой старой ссылки: howardhinnant.github.io/date.html. вот значительно улучшенная версия 2 этой библиотеки: howardhinnant.github.io/date_v2.html Вот реализация на основе github, которая в настоящее время состоит из 3 отдельных (но связанных между собой библиотек): github.com/HowardHinnant/date< /а> - person Howard Hinnant; 20.03.2016
comment
@HowardHinnant: Черт, кто-то действительно все усложнил. Его аргументы о неоднозначности date(m,d,y) с date(d,m,y) в любом случае в основном неверны, потому что у нас уже есть отличный стандарт для дат (ISO 8601), и мы могли бы адаптировать этот порядок и использовать date(y,m,d). Я уверен, что все могли бы согласиться с этим (а даже если нет, это API, а не ввод открытого текста). Бонус в том, что потенциальная перегрузка, добавляющая время, добавляет только аргументы в конце (например, date(y,m,d,hour,min,sec)). - person Tim Čas; 05.07.2016

localtime и gmtime имеют статическое внутреннее хранилище, что означает, что они не являются потокобезопасными (мы должны вернуть указатель на структуру данных, поэтому он должен быть выделен либо динамически, либо статическим значением, либо глобальным значением - поскольку динамическое выделение приведет к утечке память, что не является разумным решением, а это означает, что это должна быть глобальная или статическая переменная [теоретически можно было бы выделить и сохранить в TLS и таким образом сделать его потокобезопасным]).

У большинства систем есть альтернативы, ориентированные на многопотоковое исполнение, но они не являются частью стандартной библиотеки. Например, в Linux/Posix есть localtime_r и gmtime_r, которые принимают дополнительный параметр для результата. См., например, http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

Точно так же в библиотеках Microsoft есть gmtime_s, который также является реентерабельным и работает аналогичным образом (передавая выходной параметр в качестве входного). См. http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

Почему стандартная библиотека С++ 11 не использует эти функции? Это вы должны спросить у людей, которые написали эту спецификацию - я ожидаю, что это портативность и удобство, но я не совсем уверен.

person Mats Petersson    schedule 02.09.2014
comment
Обратите внимание, что localtime и gmtime в Microsoft Visual C Runtime используют TLS и являются потокобезопасными. (См. здесь.) Целью gmtime_s является безопасность (добавление проверки параметров), а не повторный вход (хотя тот факт, что он принимает выходные данные параметр позволяет ему также хорошо работать для этого). - person Josh Kelley; 02.09.2014

Нет потокобезопасной альтернативы std::localtime и std::gmtime, потому что вы не предложили ее и не маршалировали через весь процесс стандартизации. И никто другой тоже.

chronoЕдинственный код календаря — это код, обертывающий существующие time_t функции. Стандартизация или написание новых не входило в сферу деятельности проекта chrono. Для такой стандартизации потребуется больше времени, больше усилий и добавление дополнительных зависимостей. Простое обертывание каждой функции time_t было простым, имело мало зависимостей и было быстрым.

Они сконцентрировали свои усилия узко. И они преуспели в том, на чем сосредоточились.

Я призываю вас начать работу над <calendar> или присоединиться к таким усилиям по созданию надежного API календаря для std. Удачи и благодати!

person Yakk - Adam Nevraumont    schedule 02.09.2014

Если вы хотите использовать бесплатную стороннюю библиотеку с открытым исходным кодом, вот способ печати std::chrono::system_clock::time_point в UTC:

#include "date.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << system_clock::now() << " UTC\n";
}

Это потокобезопасная альтернатива std::gmtime с использованием современного синтаксиса C++.

Для современной поточно-ориентированной замены std::localtime вам понадобится тесно связанная библиотека часовых поясов более высокого уровня и синтаксис выглядит так:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << make_zoned(current_zone(), system_clock::now()) << "\n";
}

Оба они будут выводить с любой точностью, поддерживаемой вашим system_clock, например:

2016-07-05 10:03:01.608080 EDT

(микросекунды в macOS)

Эти библиотеки выходят далеко за рамки замены gmtime и localtime. Например, вы хотите увидеть текущую дату в юлианском календаре?

#include "julian.h"
#include <iostream>

int
main()
{
    using namespace std::chrono;
    std::cout << julian::year_month_day(date::floor<date::days>(system_clock::now())) << "\n";
}

2016-06-22

Как насчет текущего времени GPS?

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    std::cout << std::chrono::system_clock::now() << " UTC\n";
    std::cout << gps_clock::now() << " GPS\n";
}

2016-07-05 14:13:02.138091 UTC
2016-07-05 14:13:19.138524 GPS

https://github.com/HowardHinnant/date

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r0.html

Обновить

Библиотеки «date.h» и «tz.h» теперь находятся в проекте спецификации C++2a с очень небольшими изменениями, и мы надеемся, что «a» равно «0». Они будут жить в шапке <chrono> и под namespace std::chrono (и не будет date namespace).

person Howard Hinnant    schedule 05.07.2016
comment
Извините, но ваш первый пример неверен: std::cout ‹‹ system_clock::now() ‹‹ UTC\n; во-первых, он не скомпилируется, во-вторых, он не в формате UTC (если только ваш компьютер не настроен на часовой пояс UTC) - person Tadzys; 30.01.2017
comment
@Tadzys: Все приведенные выше примеры представляют собой полные минимальные программы, которые были протестированы перед публикацией с использованием упомянутых библиотек. Во-вторых: эпоха system_clock не указана. Но стандартом де-факто среди всех трех его реализаций является отслеживание времени Unix (en.wikipedia.org /wiki/Unix_time). Я пытаюсь стандартизировать эту текущую практику: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r1.html - person Howard Hinnant; 30.01.2017
comment
извините, мой плохой, не понял, что tz.h перегрузил оператор ‹‹ для структуры time_point, и да, я ошибся и во втором пункте (это может объяснить мою ошибку, которая у меня есть :) - person Tadzys; 30.01.2017
comment
@Tadzys: Возможно, эти библиотеки могут помочь с вашей ошибкой. :-) А если нет, возможно, я все равно смогу помочь. - person Howard Hinnant; 30.01.2017
comment
В библиотеке Boost ptime также есть функции для преобразования времени файла Unix в разумную дату/время, включая его печать, и, вероятно, она используется намного шире, чем эта. - person Arthur Tacca; 29.11.2020

Как уже упоминалось, на самом деле ни в одном доступном стандарте C++ нет многопоточного удобства и переносимого подхода к форматированию времени, но есть некоторая архаичная техника препроцессора, которую я нашел полезной (спасибо Андрею Александреску на CppCon 2015 слайд 17 и 18):

std::mutex gmtime_call_mutex;

template< size_t For_Separating_Instantiations >
std::tm const * utc_impl( std::chrono::system_clock::time_point const & tp )
{
    thread_local static std::tm tm = {};
    std::time_t const time = std::chrono::system_clock::to_time_t( tp );
    {
        std::unique_lock< std::mutex > ul( gmtime_call_mutex );
        tm = *std::gmtime( &time );
    }
    return &tm;
}


#ifdef __COUNTER__
#define utc( arg ) utc_impl<__COUNTER__>( (arg) )
#else
#define utc( arg ) utc_impl<__LINE__>( (arg) )
#endif 

Здесь мы объявляем функцию с аргументом шаблона size_t и возвращаем указатель на статический член std::tm. Теперь каждый вызов этой функции с другим аргументом шаблона создает новую функцию с совершенно новой статической переменной std::tm. Если определен макрос __COUNTER__, он должен заменяться увеличивающимся целочисленным значением каждый раз, когда он используется, в противном случае мы используем макрос __LINE__ и в этом случае лучше быть уверенным, что мы не вызываем макрос utc дважды в одной строке.

Глобальный gmtime_call_mutex защищает вызов std::gmtime без многопоточности в каждом экземпляре, и, по крайней мере, в Linux не должно быть проблем с производительностью, поскольку получение блокировки сначала выполняется как обход спин-блокировки, и в нашем случае никогда не должно заканчиваться реальной блокировкой потока.

thread_local гарантирует, что разные потоки, выполняющие один и тот же код с вызовами utc, будут по-прежнему работать с разными переменными std::tm.

Пример использования:

void work_with_range(
        std::chrono::system_clock::time_point from = {}
        , std::chrono::system_clock::time_point to = {}
        )
{
    std::cout << "Will work with range from "
         << ( from == decltype(from)()
              ? std::put_time( nullptr, "beginning" )
              : std::put_time( utc( from ), "%Y-%m-%d %H:%M:%S" )
            )
         << " to "
         << ( to == decltype(to)()
              ? std::put_time( nullptr, "end" )
              : std::put_time( utc( to ), "%Y-%m-%d %H:%M:%S" )
            )
         << "."
         << std::endl;
    // ...
}
person Felix Vanorder    schedule 20.03.2016
comment
Обратите внимание, что это не защитит от одновременного внешнего использования gmtime или localtime в одном и том же процессе. - person evoskuil; 22.07.2016

Boost: не совсем уверен, что это потокобезопасно, но похоже:

#include "boost/date_time/posix_time/posix_time.hpp"

std::wstring stamp = boost::posix_time::to_iso_wstring(
    boost::posix_time::second_clock::local_time());
std::wstring stamp = boost::posix_time::to_iso_wstring(
    boost::posix_time::second_clock::universal_time());

См. https://www.boost.org/doc/libs/1_75_0/doc/html/date_time/examples.html

person user1050755    schedule 19.12.2020