¿Por qué no hay una alternativa segura para subprocesos de C++ 11 a std::localtime y std::gmtime?

10 minutos de lectura

avatar de usuario
TNA

En C++ 11 todavía tienes que usar std::localtime y std::gmtime como indirección para imprimir un std::chrono::time_point. Estas funciones no son seguras de usar en un entorno de subprocesos múltiples como se introdujo con C++ 11 porque devuelven un puntero a una estructura estática interna. Esto es especialmente molesto ya que C ++ 11 introdujo la función conveniente std::put_time que es casi inutilizable por la misma razón.

¿Por qué se rompe esto tan fundamental o se me pasa por alto algo?

  • Guau, esas funciones están casi tan bien diseñadas como strtok.

    – CodesInChaos

    02/09/2014 a las 15:12

  • “La programación en C ++ no debería ser fácil” Supongo que esta sigue siendo la filosofía principal: necesita algo que escriba usted mismo a menos que sea un mínimo absoluto.

    – uuu777

    24 de marzo de 2015 a las 15:02

  • Estoy totalmente de acuerdo con tu diatriba. Hubiera sido fácil implementar std::localtime() como un contenedor alrededor de localtime_r() de GLIBC. Pero ahora está roto en el estándar.

    – Kai Petzke

    14 de diciembre de 2018 a las 9:27

avatar de usuario
CT

De acuerdo a N2661el papel que añadió <chrono>:

Este documento no ofrece servicios calendáricos excepto un mapeo mínimo hacia y desde C’s time_t.

Como este documento no propone una biblioteca de fecha/hora, ni especifica épocas, tampoco aborda los segundos bisiestos. Sin embargo, una biblioteca de fecha/hora encontrará que esto es una excelente base sobre la cual construir.

Este artículo no propone una biblioteca de cantidades físicas de propósito general.

Este artículo propone una base sólida que, en el futuro, podría proporcionar un punto de partida compatible para una biblioteca general de unidades físicas. Si bien una biblioteca futura de este tipo podría tomar cualquiera de varias formas, la propuesta actual no llega a ser realmente una biblioteca de unidades físicas. Esta propuesta es específica del tiempo y sigue estando motivada por las necesidades relacionadas con el tiempo de la biblioteca de subprocesos.

El objetivo principal de esta propuesta es satisfacer las necesidades de la API de subprocesos de biblioteca estándar de una manera que sea fácil de usar, segura de usar, eficiente y lo suficientemente flexible como para no quedar obsoleta dentro de 10 o incluso 100 años. Cada característica contenida en esta propuesta está aquí por una razón específica con casos prácticos de uso como motivación. No se han incluido las cosas que caían en la categoría de “genial”, o “que suena como que podría ser útil”, o “muy útil pero no necesario para esta interfaz”. Dichos artículos podrían aparecer en otras propuestas y posiblemente apuntar a un TR.

Tenga en cuenta que el objetivo principal de <chrono> es “para satisfacer las necesidades de la API de subprocesos de la biblioteca estándar”, que no requiere servicios de calendario.

  • @TNA Bueno, fue pensado como un reemplazo para strftimeque también necesita un tm *.

    – CT

    2 de septiembre de 2014 a las 8:41

  • En mi opinión, si reemplaza una función C por una función C ++, debería ser consistente de alguna manera, que no necesita funciones C inseguras para usarla. No existe una forma independiente de la plataforma para obtener un tm por lo que todas las funciones usando un tm son inutilizables con C++ 11 aparte de algunos casos especiales. No hay una forma estándar de evitarlo.

    – TNA

    2 de septiembre de 2014 a las 8:48


  • La verdadera razón es que cualquier cosa relacionada con el tiempo fue increíblemente controvertido en el comité, y fue un chirriador solo para obtener <chrono> estandarizado. Si también hubiéramos intentado los servicios de calendario, C++ 11 se habría llamado C++ 14 al menos. Aquí home.roadrunner.com/~hinnant/bloomington/date.html es una propuesta de calendario posterior a C++11 que el comité odiaba.

    – Howard Hinant

    02/09/2014 a las 12:51

  • @CodesInChaos: Porque el localtime/gmtime La API es más antigua que los bits. 😉

    – Howard Hinant

    02/09/2014 a las 15:34


  • El enlace que doy arriba a la “propuesta de calendario que el comité odiaba” ha muerto. Aquí hay una versión nueva de ese viejo enlace: howardhinnant.github.io/date.html Además, aquí hay una “versión 2” muy mejorada de esa biblioteca: howardhinnant.github.io/date_v2.html Aquí está la implementación basada en github actualmente compuesta por 3 bibliotecas distintas (pero relacionadas): github.com/HowardHinnant/fecha

    – Howard Hinant

    20 de marzo de 2016 a las 20:02

localtime y gmtime tienen almacenamiento interno que es estático, lo que significa que no son seguros para subprocesos (tenemos que devolver un puntero a una estructura de datos, por lo que debe asignarse dinámicamente, un valor estático o un valor global, ya que la asignación dinámica perdería memoria, eso no es una solución razonable, lo que significa que tiene que ser una variable global o estática [theoretically, one could allocate and store in TLS, and make it threadsafe that way]).

La mayoría de los sistemas tienen alternativas seguras para subprocesos, pero no forman parte de la biblioteca estándar. Por ejemplo, Linux/Posix tiene localtime_r y gmtime_r, que toma un parámetro adicional para el resultado. Ver por ejemplo
http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

Del mismo modo, las bibliotecas de Microsoft tienen gmtime_s, que también es reentrante y funciona de manera similar (pasando el parámetro de salida como entrada). Ver http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

¿Por qué la biblioteca estándar de C++ 11 no usa estas funciones? Que tendría que preguntarle a las personas que escribieron esa especificación: espero que sea portátil y conveniente, pero no estoy completamente seguro.

  • Tenga en cuenta que localtime y gmtime en Visual C Runtime de Microsoft sí usan TLS y son seguros para subprocesos. (Mira aquí.) gmtime_sEl propósito de es la seguridad (agregar validación de parámetros), no la reentrada (aunque el hecho de que tome un parámetro de salida también permite que funcione bien para esto).

    –Josh Kelley

    2 de septiembre de 2014 a las 12:48

No existe una alternativa segura para subprocesos a std::localtime y std::gmtime porque usted no propuso uno y lo dirigió a través de todo el proceso de estandarización. Y tampoco nadie más.

chronoEl único código de calendario es el código que envuelve time_t funciones Estandarizar o escribir nuevos estaba fuera del dominio de la chrono proyecto. Hacer tal estandarización requeriría más tiempo, más esfuerzo y agregaría más dependencias. Simplemente envolviendo cada time_t La función era simple, tenía pocas dependencias y era rápida.

Centraron su esfuerzo estrechamente. Y tuvieron éxito en lo que se enfocaron.

Os animo a empezar a trabajar en <calendar> o unirse a tal esfuerzo para crear una API de calendario robusta para std. ¡Buena suerte y buena suerte!

avatar de usuario
Howard Hinant

Si está dispuesto a utilizar un Biblioteca de terceros gratuita y de código abiertoaquí hay una manera de imprimir std::chrono::system_clock::time_point en UTC:

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

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

Esta es una alternativa segura para subprocesos a std::gmtime utilizando la sintaxis moderna de C++.

Para un moderno, seguro para subprocesos std::localtime Reemplazo, necesitas esto estrechamente relacionado biblioteca de zona horaria de nivel superior y la sintaxis se ve así:

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

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

Ambos producirán con la precisión que sea su system_clock apoya, por ejemplo:

2016-07-05 10:03:01.608080 EDT

(microsegundos en macOS)

Estas bibliotecas van mucho más allá de un gmtime y localtime reemplazo. Por ejemplo, ¿quieres ver la fecha actual en el calendario juliano?

#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

¿Qué hay de la hora actual del 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/fecha

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

Actualizar

Las bibliotecas “date.h” y “tz.h” ahora están en el borrador de la especificación C++2a, con cambios muy pequeños, y donde esperamos que ‘a’ sea ‘0’. Vivirán en la cabecera <chrono> Y debajo namespace std::chrono (y no habrá date namespace).

avatar de usuario
Félix Vanorder

Como otros mencionaron, realmente no hay una conveniencia segura para subprocesos y un enfoque de formato de tiempo portátil en ningún estándar C ++ disponible, pero hay una técnica de preprocesador arcaica que encontré útil (gracias a Andrei Alexandrescu en CppCon 2015 diapositiva 17 y 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 

Aquí declaramos la función con size_t argumento de plantilla y puntero de retorno al miembro estático std::tm. Ahora, cada llamada de esta función con un argumento de plantilla diferente crea una nueva función con estática nueva std::tm variable. Si __COUNTER__ macro está definida, debe ser reemplazada por un valor entero incrementado cada vez que se usa, de lo contrario usamos __LINE__ macro y en este caso mejor asegurarse de que no llamamos macro utc dos veces en una línea.

Global gmtime_call_mutex proteger sin hilos std::gmtime llame en cada instanciación, y al menos en Linux no debería ser un problema de rendimiento, ya que la adquisición de bloqueos se realiza primero como un bloqueo de giro y, en nuestro caso, nunca debería terminar con un bloqueo de subprocesos real.

thread_local asegura que diferentes subprocesos ejecutan el mismo código con utc las llamadas seguirán funcionando con diferentes std::tm variables

Ejemplo de uso:

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

  • Tenga en cuenta que esto no protegería contra el uso externo simultáneo de gmtime o localtime en el mismo proceso.

    – evoskuil

    22 de julio de 2016 a las 20:21

avatar de usuario
usuario1050755

Boost: no estoy del todo seguro de si esto es seguro para subprocesos, pero parece que sí:

#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());

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

  • Tenga en cuenta que esto no protegería contra el uso externo simultáneo de gmtime o localtime en el mismo proceso.

    – evoskuil

    22 de julio de 2016 a las 20:21

¿Ha sido útil esta solución?