Why is there no C++11 threadsafe alternative to st

2020-05-18 04:35发布

In C++11 you still have to use std::localtime and std::gmtime as indirection to print a std::chrono::time_point. These functions are not safe to use in a multithreaded environment as introduced with C++11 because they return a pointer to an internal static struct. This is especially annoying since C++11 introduced the convenient function std::put_time which is nearly unusable for the same reason.

Why is this so fundamental broken or do I overlook something?

5条回答
倾城 Initia
2楼-- · 2020-05-18 05:00

localtime and gmtime have internal storage that is static, which means they are not threadsafe (we have to return a pointer to a data structure, so it either has to be allocated dynamically, a static value or a global value - since allocating dynamically would leak memory, that is not a reasonable solution, meaning that it has to be a global or static variable [theoretically, one could allocate and store in TLS, and make it threadsafe that way]).

Most systems do have threadsafe alternatives, but they are not part of the standard library. For example, Linux/Posix has localtime_r and gmtime_r, which takes an extra parameter for the result. See for example http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

Similarly, Microsoft libraries have gmtime_s, which is also re-entrant and works in a similar way (passing in the output parameter as an input). See http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

As to why the standard C++11 library doesn't use these functions? That you'd have to ask the people who wrote that specification - I expect it's portability and convenience, but I'm not entirely sure.

查看更多
Anthone
3楼-- · 2020-05-18 05:03

As others had mentioned, there is really no threadsafe convenience and portable time formatting approach in any available C++ standard, but there is some archaic preprocessor technique I found usable (thanks to Andrei Alexandrescu at 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 

Here we declare function with size_t template argument and returning pointer to static member std::tm. Now each call of this function with different template argument create a new function with brand new static std::tm variable. If __COUNTER__ macro is defined, it should be replaced by incremented integer value each time it is used, otherwise we use __LINE__ macro and in this case better to be sure that we do not call macro utc twice in one line.

Global gmtime_call_mutex protect non-threadsafe std::gmtime call in each instantiation, and at least in Linux shouldn't be a performance problem as lock acquiring is firstly performed as running around spinlock, and in our case should never end up with real thread lock.

thread_local ensures that different threads running same code with utc calls will still working with different std::tm variables.

Example of usage:

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;
    // ...
}
查看更多
我想做一个坏孩纸
4楼-- · 2020-05-18 05:07

There is no threadsafe alternative to std::localtime and std::gmtime because you didn't propose one and marshal it through the entire standardization process. And neither did anyone else.

chronos only calendar code is code that wraps existing time_t functions. Standardizing or writing new ones was outside of the domain of the chrono project. Doing such standardization would require more time, more effort, and add more dependencies. Simply wrapping each time_t function was simple, had few dependencies, and quick.

They focused their effort narrowly. And they succeeded at what they focused on.

I encourage you to start working on <calendar> or joining such an effort to create a robust calendaring API for std. Good luck and godspeed!

查看更多
够拽才男人
5楼-- · 2020-05-18 05:16

According to N2661, the paper that added <chrono>:

This paper does not offer calendrical services except for a minimal mapping to and from C's time_t.

As this paper does not propose a date/time library, nor specify epochs, it also does not address leap seconds. However, a date/time library will find this to be an excellent foundation on which to build.

This paper does not propose a general purpose physical quantities library.

This paper proposes a solid foundation that, in the future, could provide a compatible starting point for a general physical units library. While such a future library might take any of several forms, the present proposal stops well short of actually being a physical units library. This proposal is time-specific, and continues to be motivated by the time-related needs of the threading library.

The major goal of this proposal is to satisfy the needs of the standard library threading API in a manner which is easy to use, safe to use, efficient, and flexible enough to not be obsolete 10 or even 100 years from now. Every feature contained in this proposal is here for a specific reason with practical use cases as motivation. Things that fell into the category of "cool", or "that sounds like it might be useful", or "very useful but not needed by this interface" have not been included. Such items might appear in other proposals, and possibly target a TR.

Note that the major goal of <chrono> is "to satisfy the needs of the standard library threading API", which does not require calendar services.

查看更多
男人必须洒脱
6楼-- · 2020-05-18 05:16

If you are willing to use a free, open-source 3rd party library, here is a way to print std::chrono::system_clock::time_point in UTC:

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

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

This is a thread-safe alternative to std::gmtime using modern C++ syntax.

For a modern, thread-safe std::localtime replacement, you need this closely related higher level timezone library and the syntax looks like this:

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

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

Both of these will output with whatever precision your system_clock supports, for example:

2016-07-05 10:03:01.608080 EDT

(microseconds on macOS)

These libraries go far beyond a gmtime and localtime replacement. For example, do you want to see the current date in the Julian calendar?

#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

How about the current GPS time?

#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

Update

The "date.h" and "tz.h" libraries are now in the draft C++2a specification, with very minor changes, and where we hope 'a' is '0'. They will live in the header <chrono> and under namespace std::chrono (and there will not be a date namespace).

查看更多
登录 后发表回答