线程本地存储开销

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

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

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

 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 */ } 

谢谢。

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

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

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

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

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

如果你打算这样做:

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

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

最后,线程本地存储并不是真正意味着每个线程存储大量变量(对TLS表的大小有依赖于编译器的限制),但这是您可以轻松解决的问题:将许多变量放入struct创建指向struct thread-local的指针。

我在TLS中看到的唯一问题是其可能的尺寸有限。 这取决于系统,因此您可能面临移植或扩展问题(BTW,在某些系统上可能根本不提供TLS)