web-dev-qa-db-ja.com

std :: localtimeおよびstd :: gmtimeに代わるC ++ 11スレッドセーフ代替物がないのはなぜですか?

C++ 11では、std::localtimeを印刷するには、std::gmtimeおよびstd::chrono::time_pointを間接指定として使用する必要があります。これらの関数は、内部静的構造体へのポインタを返すため、C++ 11で導入されたマルチスレッド環境で安全に使用できません。これは、C++ 11が同じ理由でほとんど使用できない便利な関数std::put_timeを導入したため、特に面倒です。

なぜこれが根本的に壊れているのか、何かを見落としているのですか?

43
TNA

N2661 によると、<chrono>を追加した論文:

このペーパーでは、Cのtime_tとの間の最小限のマッピングを除き、カレンダーサービスを提供していません。

このペーパーでは日付/時刻ライブラリの提案もエポックの指定も行っていないため、うるう秒にも対応していません。ただし、日付/時刻ライブラリは、これを構築するための優れた基盤であると判断します。

このホワイトペーパーでは、汎用物理量ライブラリを提案していません。

このホワイトペーパーでは、将来、一般的な物理ユニットライブラリの互換性のある開始点を提供できる強固な基盤を提案します。このような将来のライブラリはいくつかの形式のいずれかをとる可能性がありますが、現在の提案は、実際に物理ユニットライブラリであるということを十分にやめません。この提案は時間に固有のものであり、スレッドライブラリの時間に関連するニーズに引き続き動機付けられています。

この提案の主な目標は、標準ライブラリスレッド化APIのニーズを、使いやすく、安全で、効率的で、10年後または100年後でも時代遅れにならないほど柔軟な方法で満たすことです。この提案に含まれるすべての機能は、モチベーションとしての実用的なユースケースを持つ特定の理由のためにここにあります。 「クール」のカテゴリに分類されるもの、「便利だと思われるもの」、「このインターフェースでは必要ではないが非常に役立つもの」は含まれていません。そのようなアイテムは、他の提案に登場する可能性があり、場合によってはTRをターゲットにします。

<chrono>の主要な目標は「カレンダーライブラリサービスを必要としない標準ライブラリスレッドAPIのニーズを満たすこと」であることに注意してください。

17
T.C.

localtimeおよびgmtimeには静的な内部ストレージがあります。つまり、スレッドセーフではありません(データ構造へのポインターを返す必要があるため、動的に割り当てる必要があります。静的な値またはグローバル値-動的に割り当てるとメモリがリークするため、合理的な解決策ではありません。つまり、グローバル変数または静的変数でなければなりません[理論的には、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 を参照してください

標準C++ 11ライブラリがこれらの関数を使用しない理由は何ですか?その仕様を書いた人々に尋ねなければならないだろう-私はそれが移植性と便利さを期待しているが、私は完全に確信しているわけではない。

11
Mats Petersson

std::localtimestd::gmtimeに代わるスレッドセーフな代替手段はありません。なぜなら、あなたはそれを提案し、標準化プロセス全体を通してそれをマーシャリングしなかったからです。そして、誰もしませんでした。

chronosカレンダーコードのみは、既存のtime_t関数をラップするコードです。新しいものの標準化または作成は、chronoプロジェクトのドメイン外でした。このような標準化を行うには、より多くの時間と労力が必要になり、依存関係が増えます。各time_t関数を単純にラップすることは簡単で、依存関係がほとんどなく、迅速でした。

彼らは自分の努力を狭く集中しました。そして、彼らは焦点を当てることに成功しました。

<calendar>の作業を開始するか、stdの堅牢なカレンダーAPIを作成するこのような取り組みに参加することをお勧めします。幸運とゴッドスピード!

無料のオープンソースのサードパーティライブラリ を使用する場合は、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ではマイクロ秒)

これらのライブラリは、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

更新

「date.h」および「tz.h」ライブラリは、C++ 2a仕様のドラフトに含まれており、非常に小さな変更が加えられており、「a」が「0」であることを期待しています。それらはヘッダー<chrono>namespace std::chronoの下にあります(date namespaceはありません)。

3
Howard Hinnant

他の人が言及したように、利用可能なC++標準にはスレッドセーフの利便性とポータブルな時間フォーマットアプローチはありませんが、いくつかの古くからあるプリプロセッサ技術が使用可能です( CppCon 2015 slide 17&のAndrei Alexandrescuのおかげです) 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テンプレート引数を使用して関数を宣言し、静的メンバーstd::tmへのポインターを返します。これで、異なるテンプレート引数を使用してこの関数を呼び出すたびに、新しい静的std::tm変数を使用して新しい関数が作成されます。 __COUNTER__マクロが定義されている場合は、使用するたびにインクリメントされた整数値で置き換える必要があります。そうでない場合は、__LINE__マクロを使用します。この場合、マクロutc 1行に2回。

グローバル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;
    // ...
}
2
Felix Vanorder