スレッドローカルストレージtlsの使用

スレッドローカルストレージ(TLS)は主に、スレッド関連のデータを複数のスレッドに保存および維持するために使用されます。保存されたデータは現在のスレッドに関連付けられ、維持のためにロックを必要としません。

したがって、複数のスレッド間でリソースの競合は発生しません。主に次の方法でTLSストレージを実装する方法。

  1. gccとclangの__thread修飾子
  2. Windowsでのmsvcの__declspec(thread)修飾子
  3. pthreadライブラリpthread_setspecificpthread_getspecificインターフェイス
  4. TlsSetValueTlsGetValue

__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の低バージョンがまだたくさんあります。クロスコンパイルツールチェーンでのコンパイラのサポートの違いはまだかなり大きいです。

さらに、__threadtlsデータメンテナンスを使用するには、関連するメモリ解放の問題を手動で管理する必要があり、使用が不十分な場合、メモリリークが発生しやすくなります。

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/

おすすめ

転載: blog.csdn.net/waruqi/article/details/53201531
おすすめ