簡體   English   中英

為什么沒有 C++11 線程安全替代 std::localtime 和 std::gmtime?

[英]Why is there no C++11 threadsafe alternative to std::localtime and std::gmtime?

在 C++11 中,您仍然必須使用std::localtimestd::gmtime作為間接打印std::chrono::time_point 這些函數在 C++11 引入的多線程環境中使用並不安全,因為它們返回一個指向內部靜態結構的指針。 這尤其令人討厭,因為 C++11 引入了方便的函數std::put_time ,出於同樣的原因,該函數幾乎無法使用。

為什么這是如此基本的破壞或我忽略了什么?

根據N2661 ,添加<chrono> chrono <chrono>的論文:

除了與 C 的time_t的最小映射外,本文不提供日歷服務。

由於本文沒有提出日期/時間庫,也沒有指定紀元,因此也沒有解決閏秒問題。 然而,日期/時間庫會發現這是一個很好的構建基礎。

本文不提出通用物理量庫。

本文提出了一個堅實的基礎,將來可以為通用物理單位庫提供一個兼容的起點。 雖然這樣一個未來的圖書館可能會采用多種形式中的任何一種,但目前的提議遠沒有真正成為一個物理單位圖書館。 該提議是特定於時間的,並且繼續受到線程庫與時間相關的需求的推動。

該提案的主要目標是以一種易於使用、使用安全、高效且足夠靈活的方式來滿足標准庫線程 API 的需求,使其在 10 年甚至 100 年后不會過時。 此提案中包含的每個功能都是出於特定原因,以實際用例為動機。 屬於“酷”、“聽起來可能很有用”或“非常有用但此界面不需要”類別的東西沒有被包括在內。 此類項目可能出現在其他提案中,並且可能針對 TR。

請注意, <chrono>的主要目標是“滿足標准庫線程 API 的需求”,它不需要日歷服務。

localtimegmtime具有靜態的內部存儲,這意味着它們不是線程安全的(我們必須返回一個指向數據結構的指針,因此它必須動態分配,靜態值或全局值 - 因為動態分配會泄漏內存,這不是一個合理的解決方案,這意味着它必須是一個全局或靜態變量[理論上,可以在 TLS 中分配和存儲,並使其線程安全])。

大多數系統確實有線程安全的替代方案,但它們不是標准庫的一部分。 例如,Linux/Posix 有localtime_rgmtime_r ,它們需要一個額外的參數作為結果。 參見例如http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

類似地,Microsoft 庫有gmtime_s ,它也是可重入的並且以類似的方式工作(將輸出參數作為輸入傳遞)。 請參閱http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

至於為什么標准的 C++11 庫不使用這些函數? 您必須詢問編寫該規范的人 - 我希望它具有可移植性和便利性,但我不完全確定。

沒有std::localtimestd::gmtime線程安全替代方案,因為您沒有提出並在整個標准化過程中對其進行編組。 其他人也沒有。

chrono唯一的日歷代碼是包裝現有time_t函數的代碼。 標准化或編寫新的不在chrono項目的范圍內。 做這樣的標准化需要更多的時間、更多的努力,並添加更多的依賴。 簡單地包裝每個time_t函數很簡單,幾乎沒有依賴項,而且速度很快。

他們把精力集中在狹隘的范圍內。 他們在他們專注的事情上取得了成功。

我鼓勵您開始研究<calendar>或加入這樣的努力,為std創建一個強大的日歷 API。 祝你好運和神速!

如果您願意使用免費的、開源的 3rd 方庫,這里有一種在 UTC 中打印std::chrono::system_clock::time_point的方法:

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

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

這是使用現代 C++ 語法的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 上的微秒)

這些庫遠遠超出了gmtimelocaltime替代。 例如,您想查看儒略歷中的當前日期嗎?

#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 )。

正如其他人所提到的,在任何可用的 C++ 標准中確實沒有線程安全的便利和可移植的時間格式化方法,但是我發現有一些古老的預處理器技術可用(感謝CppCon 2015幻燈片 17 和 18 上的 Andrei Alexandrescu):

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM