スレッドローカルストレージ(TLS)は主に、スレッド関連のデータを複数のスレッドに保存および維持するために使用されます。保存されたデータは現在のスレッドに関連付けられ、維持のためにロックを必要としません。。
したがって、複数のスレッド間でリソースの競合は発生しません。主に次の方法でTLSストレージを実装する方法。
- gccとclangの
__thread
修飾子 - Windowsでのmsvcの
__declspec(thread)
修飾子 - pthreadライブラリ
pthread_setspecific
とpthread_getspecific
インターフェイス - 窓
TlsSetValue
とTlsGetValue
__threadおよび__declspec(thread)の使用
その中で、__ threadと__declspec(thread)が最も使いやすく、静的変数またはグローバル変数の前にこの修飾子を追加してから、スレッド内の変数にアクセスするだけで済みます。
例えば:
tb_void_t tb_thread_func(tb_cpointer_t priv)
{
// 定义一个线程局部变量
static __thread int a = 0;
// 初始化这个变量,设置为当前线程id
if (!a) a = tb_thread_self();
}
複数のスレッドを実行する場合、上記のコードの各スレッドの変数aの値は異なり、値は各スレッドのIDです。
そして__declspec(thread)
それらとそれは似ていますが__thread
、ライン上の交換だけです
これらの2つの修飾子は非常に使いやすいですが、コンパイラーによってサポートされる必要があります。ほとんどのプラットフォームのコンパイラーは現在それをサポートしていますが、クロスプラットフォーム開発には十分ではありません。
結局のところ、__thread
特に組み込み開発の分野では、サポートされていない可能性のあるgccの低バージョンがまだたくさんあります。クロスコンパイルツールチェーンでのコンパイラのサポートの違いはまだかなり大きいです。。
さらに、__thread
tlsデータメンテナンスを使用するには、関連するメモリ解放の問題を手動で管理する必要があり、使用が不十分な場合、メモリリークが発生しやすくなります。。
pthreadインターフェース
pthreadのtls関連のインターフェースは比較的完全であり、free関数の登録をサポートします。スレッドが終了すると、メモリリークを回避するために、関連するtlsデータが自動的に解放されますが、使用は少し複雑です。
簡単な例を見てみましょう。
// 测试线程中tls变量存储的key,需定义为全局或者static
static pthread_key_t g_local_key = 0;
static tb_void_t tb_thread_local_free(tb_pointer_t priv)
{
tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}
static tb_void_t tb_thread_local_init(tb_void_t)
{
// 创建tls的key,并且设置自动释放函数
pthread_key_create(&g_local_key, tb_thread_local_free);
}
static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
// 在所有线程中,仅执行一次,用于在线程内部初始化 tls 的 key
static pthread_once_t s_once = PTHREAD_ONCE_INIT;
pthread_once(&s_once, tb_thread_local_init);
// 尝试读取当前tls数据
tb_size_t local;
if (!(local = (tb_size_t)pthread_getspecific(g_local_key)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (0 == pthread_setspecific(g_local_key, (tb_pointer_t)self))
local = self;
}
return 0;
}
見た目はもっと複雑ですが、柔軟性があります。スレッド内にキーを作成する必要がない場合は、呼び出す必要はありません。pthread_once
作成したキーをスレッドに渡してアクセスするだけです。
TlsSetValueインターフェース
もちろん、tlsユーザーインターフェイスのウィンドウに属するこのソケット(TlsSetValue、TlsGetValue、TlsAlloc、TlsFree )は、クロスプラットフォームにすることはできず、ほとんどpthreadを使用しますが、自動的に登録することはできず、関数を解放し、提供しませんでした内部スレッドと同様のpthread_once
インターフェース
キーの作成以来、機能はわずかに不十分です。。
static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
// 创建一个tls的key,注:此处非线程安全,最好放到类似pthread_once提供的init函数中去创建
// 此处就临时先这么写了,仅仅只是为了方便描述api用法,不要照搬哦。。
static DWORD s_key = 0;
if (!s_key) s_key = TlsAlloc();
// 尝试读取当前tls数据
DWORD local;
if (!(local = TlsGetValue(s_key)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (TlsSetValue(s_key, (LPVOID)self))
local = self;
}
return 0;
}
実際、Windowsはコルーチンで使用するためのFlsAlloc、FlsSetValueシリーズのインターフェイスも提供し、登録済みの自動リリースコールバック関数をサポートしますが、システムバージョンにはいくつかの要件があり、xpなどの古いシステムは使用できません。。
ここで説明することはあまりありません。
tboxが提供するthread_local
インターフェースパッケージ
最近、tboxのtlsインターフェースが変換され、実装ロジックが再構築されました。これにより、口の使いやすさ、機能性、効率が大幅に向上しました。。
現在、次の機能がサポートされています。
- 自動解放コールバックの登録をサポートして、スレッドが終了したときにsettlsデータが自動的に解放されるようにします
- スレッド内でのスレッドセーフなキー作成をサポートする
- tboxが終了すると、作成されたすべてのキーが自動的に破棄されます。もちろん、事前にアクティブに破棄することもできます。
使い方も非常に便利です。pthreadと非常によく似ていますが、内部で自動的に呼び出されpthread_once
ます。たとえば、pthreadのように明示的に呼び出す必要はありません。
static tb_void_t tb_demo_thread_local_free(tb_cpointer_t priv)
{
tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}
static tb_int_t tb_demo_thread_local_test(tb_cpointer_t priv)
{
/* 线程安全地初始化一个tls对象,相当于key,并且注册自动free回调
*
* 注:虽然所有线程都会执行到这个tb_thread_local_init
* 但是s_local的tls对象,只会确保初始化一次,内部有类似pthread_once接口来维护
*/
static tb_thread_local_t s_local = TB_THREAD_LOCAL_INIT;
if (!tb_thread_local_init(&s_local, tb_demo_thread_local_free)) return -1;
// 尝试读取当前tls数据
tb_size_t local;
if (!(local = (tb_size_t)tb_thread_local_get(&s_local)))
{
// 设置tls数据为当前线程id
tb_size_t self = tb_thread_self();
if (tb_thread_local_set(&s_local, (tb_cpointer_t)self))
local = self;
}
return 0;
}
スレッドが終了すると、自動的に無料のコールバックが呼び出され、対応する残りのtlsデータが解放tb_exit
され、終了後に作成されたすべてのtlsオブジェクトが破棄されます。
もちろん、あなたは積極的に呼び出すことができます:tb_thread_local_exit(&s_local)
それを破壊する。。
pthreadと比較して、tboxのこのインターフェイスのセットは、initコールバック関数を減らし、Windowsよりも自動リリースメカニズムを備えており、同時にクロスプラットフォームをサポートします。。
その他の話
いくつかのライブラリ__thread
がpthreadインターフェースと混合されているのを見る前に、私は非常に説明がつかないと感じ、個人的に次のような問題があると感じました。
static __thread pthread_key_t g_key;
元のpthreadドキュメントには、キーをグローバルまたは静的に格納する必要があることが明確に記載されて__thread
いましたが、ここに追加した後、実際には、各スレッドがアクセスするキーは同じキーではありません。。
総括する
いくつかの単純なintデータをスレッド内に格納したいだけで、完全なクロスプラットフォームのサポートを考慮しない場合は、直接__thread
または__declspec(thread)
jを使用することをお勧めします。これは非常に便利で使いやすいです。
クロスプラットフォーム操作を検討したい場合は、tboxのtlsインターフェースも良い選択です。。
個人ホームページ:TBOOXオープンソースプロジェクト
元のソース:http://tboox.org/cn/2016/09/28/thread-local/