web-dev-qa-db-fra.com

Pourquoi n'y a-t-il pas d'alternative threadsafe C ++ 11 à std :: localtime et std :: gmtime?

En C++ 11, vous devez toujours utiliser std::localtime et std::gmtime comme indirection pour imprimer un std::chrono::time_point. Ces fonctions ne sont pas sûres à utiliser dans un environnement multithread comme introduit avec C++ 11 car elles renvoient un pointeur vers une structure statique interne. Ceci est particulièrement ennuyeux puisque C++ 11 a introduit la fonction pratique std::put_time qui est presque inutilisable pour la même raison.

Pourquoi est-ce si fondamental cassé ou est-ce que j'oublie quelque chose?

45
TNA

Selon N2661 , le papier qui a ajouté <chrono>:

Ce document n'offre pas de services de calendrier, sauf pour un mappage minimal vers et depuis _ time_t.

Comme cet article ne propose pas de bibliothèque date/heure, ni ne spécifie d'époques, il ne traite pas non plus des secondes intercalaires. Cependant, une bibliothèque de date/heure trouvera que c'est une excellente base sur laquelle construire.

Cet article ne propose pas de bibliothèque de quantités physiques à usage général.

Cet article propose une base solide qui, à l'avenir, pourrait fournir un point de départ compatible pour une bibliothèque générale d'unités physiques. Bien qu'une telle future bibliothèque puisse prendre plusieurs formes, la présente proposition ne fait pas vraiment office de bibliothèque d'unités physiques. Cette proposition est temporelle et continue d'être motivée par les besoins temporels de la bibliothèque de threads.

L'objectif principal de cette proposition est de satisfaire les besoins de l'API de threading de bibliothèque standard d'une manière qui est facile à utiliser, sûre à utiliser, efficace et suffisamment flexible pour ne pas être obsolète dans 10 ou même 100 ans. Chaque fonctionnalité contenue dans cette proposition est ici pour une raison spécifique avec des cas d'utilisation pratiques comme motivation. Les choses qui entraient dans la catégorie "cool", ou "qui sonne comme pouvant être utile", ou "très utile mais pas nécessaire par cette interface" n'ont pas été incluses. Ces éléments peuvent apparaître dans d'autres propositions et éventuellement cibler un TR.

Notez que l'objectif principal de <chrono> est "pour satisfaire les besoins de l'API de threading de bibliothèque standard", qui ne nécessite pas de services de calendrier.

17
T.C.

localtime et gmtime ont un stockage interne qui est statique, ce qui signifie qu'ils ne sont pas threadsafe (nous devons renvoyer un pointeur vers une structure de données, il doit donc être alloué dynamiquement, une valeur statique ou une valeur globale - puisque l'allocation dynamique entraînerait une fuite de mémoire, ce n'est pas une solution raisonnable, ce qui signifie qu'il doit s'agir d'une variable globale ou statique [en théorie, on pourrait allouer et stocker dans TLS, et le rendre threadsafe de cette façon]).

La plupart des systèmes ont des alternatives threadsafe, mais ils ne font pas partie de la bibliothèque standard. Par exemple, Linux/Posix a localtime_r et gmtime_r, qui prend un paramètre supplémentaire pour le résultat. Voir par exemple http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

De même, les bibliothèques Microsoft ont gmtime_s, qui est également rentrant et fonctionne de manière similaire (en passant le paramètre de sortie en entrée). Voir http://msdn.Microsoft.com/en-us/library/3stkd9be.aspx

Quant à savoir pourquoi la bibliothèque standard C++ 11 n'utilise pas ces fonctions? Que vous devriez demander aux personnes qui ont écrit cette spécification - je m'attends à ce que ce soit la portabilité et la commodité, mais je ne suis pas entièrement sûr.

12
Mats Petersson

Il n'y a pas d'alternative threadsafe à std::localtime et std::gmtime parce que vous n'en avez pas proposé et que vous avez organisé tout le processus de normalisation. Et personne d'autre non plus.

chronoseulement le code de calendrier est un code qui enveloppe les time_t les fonctions. La standardisation ou l'écriture de nouveaux fichiers ne faisait pas partie du domaine du projet chrono. Faire une telle normalisation nécessiterait plus de temps, plus d'efforts et ajouterait plus de dépendances. Envelopper simplement chaque time_t la fonction était simple, avait peu de dépendances et rapide.

Ils ont concentré leurs efforts étroitement. Et ils ont réussi ce sur quoi ils se sont concentrés.

Je vous encourage à commencer à travailler sur <calendar> ou participer à un tel effort pour créer une API de calendrier robuste pour std. Bonne chance et bonne chance!

6

Si vous souhaitez utiliser une bibliothèque tierce gratuite et open-source , voici un moyen d'imprimer 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";
}

Il s'agit d'une alternative thread-safe à std::gmtime en utilisant la syntaxe C++ moderne.

Pour un fil moderne et sûr std::localtime remplacement, vous avez besoin de cette relation étroitement liée bibliothèque de fuseaux horaires de niveau supérieur et la syntaxe ressemble à ceci:

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

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

Ces deux éléments sortiront avec la précision de votre system_clock prend en charge, par exemple:

2016-07-05 10:03:01.608080 EDT

(microsecondes sur macOS)

Ces bibliothèques vont bien au-delà d'un remplacement de gmtime et localtime. Par exemple, voulez-vous voir la date actuelle dans le calendrier julien?

#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

Que diriez-vous de l'heure GPS actuelle?

#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

Mise à jour

Les bibliothèques "date.h" et "tz.h" sont maintenant dans le projet de spécification C++ 2a, avec des modifications très mineures, et où nous espérons que "a" est "0". Ils vivront dans l'en-tête <chrono> et sous namespace std::chrono (et il n'y aura pas de date namespace).

4
Howard Hinnant

Comme d'autres l'avaient mentionné, il n'y a vraiment aucune approche pratique de threadsafe et de formatage de l'heure portable dans aucun standard C++ disponible, mais il y a une technique de préprocesseur archaïque que j'ai trouvée utilisable (merci à Andrei Alexandrescu sur CppCon 2015 slide 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 

Ici, nous déclarons la fonction avec size_t argument de modèle et retour du pointeur au membre statique std::tm. Maintenant, chaque appel de cette fonction avec un argument de modèle différent crée une nouvelle fonction avec une toute nouvelle statique std::tm variable. Si __COUNTER__ la macro est définie, elle doit être remplacée par une valeur entière incrémentée à chaque utilisation, sinon nous utilisons __LINE__ macro et dans ce cas, mieux vaut être sûr de ne pas appeler macro utc deux fois sur une même ligne.

Global gmtime_call_mutex protéger non threadsafe std::gmtime appeler à chaque instanciation, et au moins sous Linux ne devrait pas être un problème de performance car l'acquisition de verrou est d'abord effectuée comme tournant autour de spinlock, et dans notre cas ne devrait jamais se retrouver avec un vrai verrou de thread.

thread_local garantit que différents threads exécutant le même code avec les appels utc continueront de fonctionner avec différents std::tm variables.

Exemple d'utilisation:

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