20

この質問は、私が何年も続けてきた慣習に疑問を投げかけました。

関数ローカル静的定数オブジェクトのスレッドセーフな初期化のために、オブジェクトの実際の構築を保護しますが、それを参照する関数ローカル参照の初期化は保護しません。このようなもの:

namespace {
  const some_type& create_const_thingy()
  {
     lock my_lock(some_mutex);
     static const some_type the_const_thingy;
     return the_const_thingy;
  }
}

void use_const_thingy()
{
  static const some_type& the_const_thingy = create_const_thingy();

  // use the_const_thingy

}

ロックには時間がかかるという考え方であり、参照が複数のスレッドによって上書きされても問題ありません。

これがあれば興味があります

  1. 実際には十分安全ですか?
  2. ルールに従って安全ですか?(私は知っています、現在の標準は「並行性」が何であるかさえ知りませんが、すでに初期化された参照を踏みにじることはどうですか?そしてPOSIXのような他の標準はこれに関連する何かを言いますか?)

これを知りたいのは、コードをそのままにしておくことができるのか、それとも戻って修正する必要があるのか​​を知りたいからです。


探究心のために:

私が使用したそのような関数ローカル静的constオブジェクトの多くは、最初の使用時にconst配列から初期化され、ルックアップに使用されるマップです。たとえば、タグ名の文字列がenum値にマップされるXMLパーサーがいくつかあるので、後でswitchタグのenum値を上書きできます。


代わりに何をすべきかについていくつかの答えを得ましたが、実際の質問に対する答えが得られなかったので(上記の1.と2を参照)、これについて報奨金を開始します。繰り返しますが、私は代わり
に何ができるかに興味がありません。私はこれについて本当に知りたいです

4

8 に答える 8

14

これは私の2回目の答えです。私はあなたの最初の質問にのみ答えます:

  1. 実際には十分安全ですか?

いいえ。自分自身を述べているので、オブジェクトの作成が保護されていることを確認しているだけであり、オブジェクトへの参照の初期化は保護されていません。

C ++ 98メモリモデルがなく、コンパイラベンダーからの明示的なステートメントがない場合、実際の参照を表すメモリへの書き込みと、初期化フラグの値を保持するメモリへの書き込み(実装方法)参照用は、複数のスレッドから同じ順序で表示されます。

また、あなたが言うように、同じ値で参照を数回上書きしても意味的な違いはありません(単語のティアリングが存在する場合でも、プロセッサアーキテクチャでは一般的に不可能であり、おそらく不可能です)が、重要な場合が1つあります。プログラムの実行中に、複数のスレッドが初めて関数を呼び出すために競合します。この場合、実際の参照が初期化される前に、これらのスレッドの1つ以上が初期化フラグが設定されていることを確認できます。

プログラムに潜在的なバグがあり、それを修正する必要があります。最適化に関しては、ダブルチェックのロックパターンを使用する以外にも多くのことがあると確信しています。

于 2010-06-22T06:23:51.983 に答える
5

これが私の見解です(スレッドが起動する前に実際に初期化できない場合):

boost :: onceを使用して、静的初期化を保護するためにこのようなものを見た(そして使用した)

#include <boost/thread/once.hpp>

boost::once_flag flag;

// get thingy
const Thingy & get()
{
    static Thingy thingy;

    return thingy;
}

// create function
void create()
{
     get();
}

void use()
{
    // Ensure only one thread get to create first before all other
    boost::call_once( &create, flag );

    // get a constructed thingy
    const Thingy & thingy = get(); 

    // use it
    thingy.etc..()          
}

私の理解では、この方法では、静的変数を作成するスレッドを除いて、すべてのスレッドがboost::call_onceを待機します。一度だけ作成され、その後呼び出されることはありません。そして、あなたはもうロックを持っていません。

于 2010-06-02T09:14:00.303 に答える
3

したがって、仕様の関連部分は6.7/4です。

実装は、名前空間スコープ(3.6.2)で静的ストレージ期間を持つオブジェクトを静的に初期化することが許可されているのと同じ条件下で、静的ストレージ期間を持つ他のローカルオブジェクトの早期初期化を実行することが許可されます。それ以外の場合、そのようなオブジェクトは、コントロールがその宣言を最初に通過するときに初期化されます。このようなオブジェクトは、初期化の完了時に初期化されたと見なされます。

2番目の部分が()を保持していると仮定すると、object is initialized the first time control passes through its declarationコードはスレッドセーフと見なすことができます。

3.6.2を読むと、許可されている初期初期化は動的初期化静的初期化に変換しているようです。静的初期化は動的初期化の前に発生する必要があり、動的初期化に到達するまでスレッドを作成する方法が考えられないため、このような早期初期化は、コンストラクターが1回呼び出されることも保証します。

アップデート

some_typeしたがって、のコンストラクターの呼び出しに関してはthe_const_thingy、コードはルールに従って正しいです。

これにより、仕様で確実にカバーされていない参照の上書きに関する問題が残ります。とは言うものの、参照がポインターを介して実装されていると想定する場合(これが最も一般的な方法であると私は信じています)、ポインターを既に保持している値で上書きするだけです。だから私の考えは、これは実際には安全なはずだということです。

于 2010-06-21T06:51:13.657 に答える
0

簡単に言えば、私はそれを考えます:

  • 「create_const_thingy」と入力すると「some_mutex」が完全に構築されると仮定すると、オブジェクトの初期化はスレッドセーフです。

  • 「use_const_thingy」内のオブジェクト参照の初期化は、スレッドセーフであることが保証されていません。(あなたが言うように)それは複数回初期化される可能性がありますが(これはそれほど問題ではありません)、未定義の動作を引き起こす可能性のある単語のティアリングの影響を受ける可能性もあります。

[C ++参照は、ポインター値を使用して実際のオブジェクトへの参照として実装されていると想定しています。ポインター値は、理論的には部分的に書き込まれると読み取ることができます]。

だから、あなたの質問に答えてみるには:

  1. 実際には十分に安全:可能性は非常に高いですが、最終的には、ポインターのサイズ、プロセッサー・アーキテクチャー、およびコンパイラーによって生成されたコードに依存します。ここで重要なのは、ポインタサイズの書き込み/読み取りがアトミックであるかどうかである可能性があります。

  2. ルールに従って安全:まあ、C ++ 98にはそのようなルールはありません、申し訳ありません(しかし、あなたはすでにそれを知っていました)。


更新:この回答を投稿した後、私はそれが実際の問題の小さな、難解な部分にのみ焦点を当てていることに気付きました。そのため、内容を編集する代わりに別の回答を投稿することにしました。質問との関連性があるので、内容は「現状のまま」のままにしておきます(また、謙虚になり、答える前にもう少し考えてみることを思い出させます)。

于 2010-06-21T06:53:14.750 に答える
0

私は標準主義者ではありません...

しかし、あなたが言及した用途のために、スレッドが作成される前にそれらを単純に初期化してみませんか?多くのシングルトンの問題は、ライブラリがロードされたときに値を単純にインスタンス化できる一方で、慣用的な「シングルスレッド」の遅延初期化を使用するために発生します(通常のグローバルのように)。

怠惰な方法は、別の「グローバル」からこの値を使用する場合にのみ意味があります。

一方、私が見た別の方法は、ある種の調整を使用することでした。

  • ライブラリのロード時に「GlobalInitializer」オブジェクトに初期化メソッドを登録する「Singleton」
  • スレッドが起動される前に「main」で呼び出される「GlobalInitializer」

私はそれを正確に説明していないかもしれませんが。

于 2010-06-02T09:29:04.087 に答える
0

悪夢を見るのに十分なプロセス間ソケットをプログラムしました。DDR RAMを搭載したCPUでスレッドセーフにするためには、データ構造をキャッシュラインアラインメントし、すべてのグローバル変数を可能な限り少ないキャッシュラインに連続してパックする必要があります。

アラインされていないプロセス間データと緩くパックされたグローバルの問題は、キャッシュミスからのエイリアシングを引き起こすことです。DDR RAMを使用するCPUには、(通常は)64バイトのキャッシュラインが多数あります。キャッシュラインをロードすると、DDR RAMは自動的にさらに多くのキャッシュラインをロードしますが、最初のキャッシュラインは常に最もホットです。高速で発生する割り込みで何が起こるかというと、キャッシュページはアナログ信号の場合と同じようにローパスフィルターとして機能し、完全につながる割り込みデータをフィルターで除去します何が起こっているのかわからない場合は、バグを困惑させます。同じことが、密に詰め込まれていないグローバル変数にも当てはまります。複数のキャッシュラインを使用する場合、重要なプロセス間変数のスナップショットを取得し、それらをスタックとレジスタに渡してデータが正しく同期されるようにしない限り、同期がとれなくなります。

.bssセクション(つまり、グローバル変数が格納されている場所はすべてゼロに初期化されますが、コンパイラーはデータをキャッシュラインアラインしません。自分で行う必要があります。これも適切な場所です。C ++コンストラクトをインプレースで使用します。ポインターを整列させる最速の方法の背後にある数学を学ぶには、この記事を読んでください;私はそのトリックを思いついたかどうかを理解しようとしています。コードは次のようになります。

inline char* AlignCacheLine (char* buffer) {
  uintptr_t offset = ((~reinterpret_cast<uintptr_t> (buffer)) + 1) & (63);
  return buffer + offset;
}

char SomeTypeInit (char* buffer, int param_1, int param_2, int param_3) {
  SomeType type = SomeType<AlignCacheLine (buffer)> (1, 2, 3);
  return 0xff;
}

const SomeType* create_const_thingy () {
  static char interprocess_socket[sizeof (SomeType) + 63],
              dead_byte = SomeTypeInit (interprocess_socket, 1, 2, 3);
  return reinterpret_cast<SomeType*> (AlignCacheLine (interprocess_socket));
}

私の経験では、参照ではなくポインタを使用する必要があります。

于 2018-06-13T03:23:02.677 に答える
-1

スレッドの作成を開始する前に関数を呼び出すだけで、参照とオブジェクトが保証されます。あるいは、そのような本当にひどいデザインパターンを使用しないでください。つまり、なぜ地球上に静的オブジェクトへの静的参照があるのですか?なぜ静的オブジェクトさえあるのですか?これには何のメリットもありません。シングルトンはひどい考えです。

于 2010-06-21T07:44:02.047 に答える
-1

これは、すべてのミューテックスシャナニガンを必要とせずに、私が考えることができる最も簡単でクリーンなアプローチのようです。

static My_object My_object_instance()
{
    static My_object  object;
    return object;
}

// Ensures that the instance is created before main starts and creates any threads
// thereby guaranteeing serialization of static instance creation.
__attribute__((constructor))
void construct_my_object()
{
    My_object_instance();
}
于 2012-06-12T20:10:50.687 に答える