线程本地存储开销
假设有一些使用全局变量的不可重入函数:
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)