先看一段代码,猜猜 Linux 下编译后执行会发生什么(编译参数 -pthread -O2 -std=c++11
,在线执行):
#include <iostream>
#include <thread>
thread_local char tls[8 << 20];
void func() {
char arr[1024 * 6];
arr[sizeof(arr) - 1] = 'A';
printf("%p %p\n", &tls, &arr);
}
int main() { std::thread(func).join(); }
如果你系统的栈大小是默认的 8MB,那么上述代码会导致栈溢出。原因在于 pthread 实现中 TLS 和线程栈使用的是同一块内存空间,TLS 使用的多时留给线程栈的就少了。
pthread 初始化时会根据系统设定的栈大小、TLS 占用的内存空间大小计算默认的栈空间:
// 1. 获取 TLS 内存大小
size_t static_tls_align;
_dl_get_tls_static_info (&__static_tls_size, &static_tls_align);
// 2. 获取系统栈大小
if (__getrlimit (RLIMIT_STACK, &limit) != 0 || limit.rlim_cur == RLIM_INFINITY) {
limit.rlim_cur = ARCH_STACK_DEFAULT_SIZE;
}
// 3. 设定 pthread 默认栈大小
const size_t minstack = pagesz + __static_tls_size + MINIMAL_REST_STACK;
if (limit.rlim_cur < minstack) {
limit.rlim_cur = minstack;
}
limit.rlim_cur = ALIGN_UP (limit.rlim_cur, pagesz);
lll_lock (__default_pthread_attr_lock, LLL_PRIVATE);
__default_pthread_attr.stacksize = limit.rlim_cur;
__default_pthread_attr.guardsize = GLRO (dl_pagesize);
lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);
pthread_create
时如果没有指定使用的栈大小,则会使用上述计算的默认栈大小:
if (attr->stacksize != 0) {
size = attr->stacksize;
} else {
lll_lock (__default_pthread_attr_lock, LLL_PRIVATE);
size = __default_pthread_attr.stacksize;
lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);
}
如果担心 TLS 触发栈溢出,可以在程序启动时通过下方代码获取 TLS 需要使用的内存空间总大小(在线执行),可以根据该值进行阈值的判断:
#include <iostream>
extern "C" void _dl_get_tls_static_info(size_t *sizep, size_t *alignp);
int main() {
size_t size;
size_t align;
_dl_get_tls_static_info(&size, &align);
printf("size %lu\n", size);
}