【问题标题】:thread-local storage overhead线程本地存储开销
【发布时间】:2011-03-27 16:51:52
【问题描述】:

假设有一些使用全局变量的不可重入函数:


int i;
void foo(void){
/* modify i */
}

然后,我想在多线程代码中使用这个函数,所以我可以这样改变代码:


void foo(int i){
/* modify i */
}

或者,通过使用 gcc __thread 说明符,更简单:


__thread int i;
void foo(void){
/* modify i */
}

最后一个的优点是我不需要更改另一个调用 foo() 的代码。

我的问题是,线程本地存储的开销是多少? TLS有一些不明显的问题吗?

如果我将通过单独的指针修改 TLS`ed 变量,是否会有一些开销,如下所示:


__thread int i;
void foo(void){
int *p = &i;
/* modify i using p pointer */
}

谢谢。

【问题讨论】:

    标签: c multithreading thread-local-storage


    【解决方案1】:

    然后,我想使用这个功能 在多线程代码中,所以我可以改变 这样编码:

    void foo(int i){
        /* modify i */
    }
    

    这肯定不会起作用,因为您只会修改i 的副本。如果您希望更改保持不变,则需要传递 int*int&

    使用 TLS 肯定不会导致任何显着的开销(空间或时间)超过您可能采用的任何自定义方法来实现相同的功能。编译器在实践中通过在保存线程局部变量的全局数据结构中动态分配存储“槽”来实现 TLS。

    当您在运行时访问线程局部变量时,有一个额外的间接级别:首先运行时必须访问当前线程的适当线程局部变量表,然后从表中取出值.这种获取是使用数组中的索引完成的(这是一个 O(1) 操作)。

    如果您打算这样做:

    __thread int i;
    void foo(void){
        int *p = &i;
        /* modify i using p pointer */
    }
    

    那么就不需要使用指针访问i。将i 视为一个全局变量,它对每个正在运行的线程具有不同的值。您不需要通过指针访问普通的全局变量来进行更改,因此也不需要使用带有线程局部变量的指针。

    最后,线程本地存储并不是真的要为每个线程存储大量变量(TLS 表的大小存在编译器相关的限制),但这是您可以轻松解决的问题:将许多变量放入一个struct 并创建一个指向struct 线程本地的指针。

    【讨论】:

    • 你是对的,这个例子还有另一个问题,这也是我想使用TLS的原因。
    • 这并不完全正确。 TLS 确实具有显着的累积开销,因为增加 TLS 的数量会增加每个线程的线程创建成本,即使是永远不会使用需要 TLS 的代码的线程。您的解决方法也不起作用。如果 TLS 有大小限制,则将数据放入 struct 不会减小它的大小。也许您正在考虑在 TLS 中放置一个指针并使用 malloc 分配 struct
    • @R..:我没有大规模线程应用程序的经验,因此不能真正说出线程创建成本的大小。然而,那些预期会在其生命周期内启动大量线程的应用程序肯定会转向诸如线程池之类的开销较小的解决方案吗?关于 TLS 中的 struct 是的,这就是想法——尽管解释不好。感谢您的捕获,我正确地重写了它。
    • 使用线程池会增加很多复杂性(对于许多人来说,使用线程是一个很大的障碍),以适应 糟糕的遗留实现,其中线程创建是缓慢的。 POSIX 线程真正shine 的情况是,您可以随意创建线程作为库代码的一部分,而调用者甚至不知道或关心它(更不用说像线程这样在丑陋的全局状态下管理它)池)。
    【解决方案2】:

    我看到 TLS 的唯一问题是它的大小可能有限。这取决于系统,因此您可能会面临移植或扩展问题(顺便说一句,TLS 在某些系统上可能根本不可用)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-03
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 2011-01-04
      • 2015-05-16
      相关资源
      最近更新 更多