18

この質問から、Javaや.NETなどの高レベルの開発フレームワークでのスレッドローカルストレージについて疑問に思いました。

JavaにはThreadLocal<T>クラス(およびおそらく他の構造)がありますが、.NETにはデータスロットがあり、まもなくThreadLocal<T>独自のクラスがあります。(これもありますが、ThreadStaticAttributeメンバーデータのスレッドローカルストレージに特に興味があります。)他のほとんどの最新の開発環境では、言語レベルまたはフレームワークレベルで1つ以上のメカニズムが提供されます。

スレッドローカルストレージはどのような問題を解決しますか、またはスレッドローカルデータを含む個別のオブジェクトインスタンスを作成する標準のオブジェクト指向イディオムに対してスレッドローカルストレージはどのような利点を提供しますか?言い換えれば、これはどうですか?

// Thread local storage approach - start 200 threads using the same object
// Each thread creates a copy of any thread-local data
ThreadLocalInstance instance = new ThreadLocalInstance();
for(int i=0; i < 200; i++) {
    ThreadStart threadStart = new ThreadStart(instance.DoSomething);
    new Thread(threadStart).Start();
}

これより優れていますか?

// Normal oo approach, create 200 objects, start a new thread on each
for(int i=0; i < 200; i++) {
    StandardInstance standardInstance = new StandardInstance();
    ThreadStart threadStart = new ThreadStart(standardInstance.DoSomething);      
    new Thread(threadStart).Start();
}

スレッドローカルストレージで単一のオブジェクトを使用すると、メモリ効率がわずかに向上し、割り当て(および構造)が少なくなるため、必要なプロセッサリソースが少なくなることがわかります。他に利点はありますか?

4

6 に答える 6

12

スレッドローカルストレージはどのような問題を解決しますか、またはスレッドローカルデータを含む個別のオブジェクトインスタンスを作成する標準のオブジェクト指向イディオムに対してスレッドローカルストレージはどのような利点を提供しますか?

スレッドローカルストレージを使用すると、実行中の各スレッドにクラスの一意のインスタンスを提供できます。これは、スレッドセーフでないクラスを操作する場合や、共有状態が原因で発生する可能性のある同期要件を回避する場合に非常に役立ちます。

利点と例については、単一のスレッドを生成する場合、インスタンスを渡すよりもスレッドローカルストレージを使用する利点はほとんどまたはまったくありません。 ThreadLocal<T>ただし、ThreadPoolを(直接的または間接的に)操作する場合、同様の構造は非常に価値があります。

たとえば、最近取り組んだ特定のプロセスがあり、.NETの新しいタスク並列ライブラリを使用して非常に重い計算を行っています。実行された計算の特定の部分をキャッシュすることができ、キャッシュに特定の一致が含まれている場合、1つの要素を処理するときにかなりの時間を節約できます。ただし、キャッシュされた情報には高いメモリ要件があったため、最後の処理ステップより多くをキャッシュしたくありませんでした。

ただし、このキャッシュをスレッド間で共有しようとすると問題が発生します。そのためには、アクセスを同期する必要があります。また、クラス内にいくつかのチェックを追加して、スレッドセーフにする必要があります。

これを行う代わりに、アルゴリズムを書き直して、各スレッドがで独自のプライベートキャッシュを維持できるようにしましたThreadLocal<T>。これにより、スレッドはそれぞれ独自のプライベートキャッシュを維持できます。TPLが使用するパーティション化スキームは要素のブロックをまとめる傾向があるため、各スレッドのローカルキャッシュには、必要な適切な値が含まれる傾向がありました。

これにより、同期の問題が解消されましたが、キャッシュを維持することもできました。この状況では、全体的なメリットは非常に大きかった。

より具体的な例については、TPLを使用した集計について書いたこのブログ投稿をご覧ください。内部的には、Parallelクラスは、ローカル状態(およびメソッドも)を保持するForEachオーバーロードをThreadLocal<TLocal>使用するたびにを使用します。これは、ロックを回避するためにローカル状態がスレッドごとに分離されている方法です。Parallel.For<TLocal>

于 2010-02-04T22:37:05.340 に答える
6

たまに、スレッドローカル状態があると便利です。1つの例は、ログコンテキストの場合です。現在サービスを提供しているリクエストなどのコンテキストを設定すると、すべてのログを照合してそのリクエストを処理できるようになります。

もう1つの良い例はSystem.Random、.NETです。使用するたびに新しいインスタンスを作成するべきではないことはかなり一般的な知識です。そのRandomため、単一のインスタンスを作成して静的変数に入れる人もいます...しかし、Randomスレッドセーフではないため、これは厄介です。代わりに、適切にシードされた、スレッドごとに1つのインスタンスが本当に必要です。ThreadLocal<T>これに最適です。

同様の例は、スレッドに関連付けられたカルチャ、またはセキュリティコンテキストです。

一般的に、それはあちこちにあまり多くのコンテキストを渡したくない場合です。すべてのメソッド呼び出しに「RandomContext」または「LogContext」を含めることができますが、APIのクリーンさが妨げられ、別のAPIを呼び出す必要がある場合は、チェーンが切断されます仮想メソッドまたは同様のものを介してあなたのもの。

私の見解では、スレッドローカルデータは可能な限り避けるべきものですが、たまにそれが本当に役立つ場合があります。

ほとんどの場合、静的である必要はありませんが、インスタンスごと、スレッドごとの情報が必要になる場合があります。繰り返しになりますが、それがどこで役立つかを確認するためにあなたの判断を使用する価値があります。

于 2010-02-04T20:02:24.057 に答える
4

スタックに値を渡すのに役立ちます。コールスタックで値が必要な場合に便利ですが、メソッドのパラメーターとして必要な場所にこの値を渡す方法(または利点)はありません。現在のHttpRequestをThreaLocalに格納する上記の例は、この良い例です。代わりに、HttpRequestをパラメーターとしてスタックから必要な場所に渡すこともできます。

于 2010-02-04T20:15:27.270 に答える
3

Javaでは、スレッドローカルストレージは、通常、単一のリクエストが特定のスレッドによって処理されるWebアプリケーションで役立ちます。たとえば、Spring Securityを例にとると、セキュリティフィルタは認証を実行してから、ユーザーの資格情報をスレッドローカル変数に格納します。

これにより、実際のリクエスト処理コードは、コードに他に何も挿入しなくても、現在のユーザーのリクエスト/認証情報にアクセスできます。

于 2010-02-04T20:02:40.463 に答える
1

ThreadLocalの実際の使用法は次のとおりです。http://blogs.captechconsulting.com/blog/balaji-muthuvarathan/persistence-pattern-using-threadlocal-and-ejb-interceptors

于 2010-07-15T15:29:29.697 に答える
1

ユビキタスにいくつかの変数にアクセスして、一連の呼び出しを行いたいとします。すべての呼び出しで引数として渡すことができます

function startComputingA(other args) {
  global_v = create // declared locally
  call A2(other args, global_v)
  call A3(other args, global_v)

function A2(other args, global_v) {
  call A3(other args, global_v)

function A3(other args, global_v) {
  call A4(other args, global_v)

global_vすべての関数は引数を宣言する必要があります。これは最悪だ。グローバル変数を格納し、それをすべてのルーチンに「仮想的に」ルーティングするためのグローバルスコープがあります

variable global_v;
function A() { // use global_v and call B() }
function B() { // use global_v and call C() }

ただし、その間に別のスレッドがこれらの関数の一部を実行し始める場合があります。これにより、グローバル変数が破損します。したがって、スレッド間ではなく、すべてのルーチンで変数をグローバルに表示する必要があります。すべてのスレッドにの個別のコピーが必要ですglobal_v。これがローカルストレージが不可欠な場合です!global_vスレッドローカル変数として宣言します。したがって、どのスレッドもglobal_vどこからでもアクセスできますが、そのコピーは異なります。

于 2013-01-10T16:08:58.200 に答える