线程本地存储开销

S.J*_*.J. 6 c multithreading thread-local-storage

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


int i;
void foo(void){
/* modify i */
}
Run Code Online (Sandbox Code Playgroud)

然后,我想在多线程代码中使用此函数,因此我可以这样更改代码:


void foo(int i){
/* modify i */
}
Run Code Online (Sandbox Code Playgroud)

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


__thread int i;
void foo(void){
/* modify i */
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


__thread int i;
void foo(void){
int *p = &i;
/* modify i using p pointer */
}
Run Code Online (Sandbox Code Playgroud)

谢谢.

Jon*_*Jon 10

然后,我想在多线程代码中使用此函数,因此我可以这样更改代码:

void foo(int i){
    /* modify i */
}
Run Code Online (Sandbox Code Playgroud)

这肯定不会起作用,因为你只会修改一份副本i.如果您希望更改坚持,则需要传递int*int&更改.

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

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

如果你打算这样做:

__thread int i;
void foo(void){
    int *p = &i;
    /* modify i using p pointer */
}
Run Code Online (Sandbox Code Playgroud)

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

最后,线程局部存储并不是真正意味着每个线程存储大量变量(对TLS表的大小有依赖于编译器的限制),但这是你可以轻松解决的问题:将许多变量放入a struct和make中指向struct线程本地的指针.

  • 这不完全正确.TLS确实具有显着的累积开销,因为增加TLS的数量会增加每个线程的线程创建成本*,甚至是永远不会使用需要TLS的代码的线程.您的解决方法也不起作用.如果对TLS有大小限制,将数据放在`struct`中将不会减小它的大小.也许您正在考虑将指针放在TLS中并使用`malloc`分配`struct`? (2认同)