Подтвердить что ты не робот

Почему нет альтернативы stf:: localtime и std:: gmtime для С++ 11 threadsafe?

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

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

4b9b3361

Ответ 1

В соответствии с N2661 документ, который добавил <chrono>:

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

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

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

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

Основная цель этого предложения - удовлетворить потребности стандартный API-интерфейс потоковой библиотеки, который прост в использовании, безопасен использовать, эффективно и достаточно гибко, чтобы не быть устаревшими 10 или даже Через 100 лет. Каждая функция, содержащаяся в этом предложении, находится здесь по определенной причине в практических случаях использования в качестве мотивации. вещи который попал в категорию "круто", или "похоже, что это может быть полезным", или "очень полезно, но не нужно этим интерфейсом", не были включены. Такие пункты могут фигурировать в других предложениях, и возможно, нацеливается на TR.

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

Ответ 2

localtime и gmtime имеют внутреннее хранилище, которое является статическим, что означает, что они не являются потокобезопасными (мы должны вернуть указатель на структуру данных, так что он либо должен быть распределен динамически, либо статическое значение, либо глобальное value - так как динамическое распределение утечки будет утечкой памяти, это не разумное решение, а это означает, что это должна быть глобальная или статическая переменная [теоретически, можно было бы выделить и сохранить в 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 не использует эти функции? Чтобы вы спросили людей, написавших эту спецификацию, я ожидаю, что это мобильность и удобство, но я не совсем уверен.

Ответ 3

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

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

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

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

Ответ 4

Как уже упоминалось, в любом доступном стандарте С++ на самом деле нет потокобезопасного удобного и переносимого формата форматирования времени, но есть некоторые архаичные методы препроцессора, которые я нашел применимыми (спасибо Андрею Александреску в 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 template и возвращаем указатель на статический член std::tm. Теперь каждый вызов этой функции с другим аргументом шаблона создает новую функцию с новой статической переменной std::tm. Если макрос __COUNTER__ определен, он должен быть заменен приращенным целочисленным значением каждый раз, когда он используется, в противном случае мы используем макрос __LINE__, и в этом случае лучше убедиться, что мы не вызываем макрос utc дважды в одной строке.

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

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;
    // ...
}

Ответ 5

Если вы хотите использовать бесплатную стороннюю библиотеку с открытым исходным кодом , вот способ печати 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 с использованием современного синтаксиса С++.

Для современной, потокобезопасной замены 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