(2020 年 9 月 26 日添加)
2009 年 10 月 24 日,as @pixelbeat first pointed out here、Bruno Haible empirically discovered the following default thread stack sizes 用于多个系统。他说在多线程程序中,“默认线程堆栈大小是:”
- glibc i386, x86_64 7.4 MB
- Tru64 5.1 5.2 MB
- Cygwin 1.8 MB
- Solaris 7..10 1 MB
- MacOS X 10.5 460 KB
- AIX 5 98 KB
- OpenBSD 4.0 64 KB
- HP-UX 11 16 KB
请注意,以上单位均为 MB 和 KB(以 1000 为基数),而不是 MiB 和 KiB(以 1024 为基数)。我已经通过验证 7.4 MB 的情况向自己证明了这一点。
他还表示:
32 KB 超出了您在多线程程序中可以安全地在堆栈上分配的空间
他说:
而 sigaltstack 的默认堆栈大小 SIGSTKSZ 是
- 在某些平台上只有 16 KB:IRIX、OSF/1、Haiku。
- 在某些平台上只有 8 KB:glibc、NetBSD、OpenBSD、HP-UX、Solaris。
- 在某些平台上只有 4 KB:AIX。
布鲁诺
他编写了以下简单的 Linux C 程序来凭经验确定上述值。您可以立即在您的系统上运行它以快速查看您的最大线程堆栈大小是多少,或者您可以在 GDBOnline 上在线运行它:https://onlinegdb.com/rkO9JnaHD。
解释:它只是创建一个新线程,以便检查线程堆栈大小而不是程序堆栈大小,在如果它们不同,那么它会使用Linux alloca() call 在堆栈(不是堆)上重复分配 128 个字节的内存,然后将 0 写入这个新的第一个字节内存块,然后打印出它分配的总字节数。它重复这个过程,每次在堆栈上再分配 128 个字节,直到程序因Segmentation fault (core dumped) 错误而崩溃。最后打印的值是您的系统允许的估计最大线程堆栈大小。
重要提示:alloca() 在堆栈上分配:尽管这看起来像在堆上动态分配内存,类似于malloc() 调用,alloca() 不会动态分配到堆上。相反,alloca() 是一个专门的 Linux 函数,用于“伪动态”(我不确定我会如何称呼它,所以这是我选择的术语)直接分配 到堆栈上 就好像它是静态分配的内存。 alloca() 使用和返回的堆栈内存在 函数级别 范围内,因此“在调用 alloca() 的 函数 返回其调用者时自动释放。 "这就是为什么每次完成for 循环迭代并且到达for 循环范围的末尾时,它的静态作用域不会退出并且alloca() 分配的内存不会被释放。有关详细信息,请参阅man 3 alloca。以下是相关引述(已添加重点):
描述
alloca() 函数在调用者的堆栈帧 中分配size 个字节的空间。当调用alloca()的函数返回给它的调用者时,这个临时空间会自动释放。
返回值
alloca() 函数返回一个指向分配空间开头的指针。 如果分配导致堆栈溢出,则程序行为未定义。
这是 Bruno Haible 2009 年 10 月 24 日的节目,copied directly from the GNU mailing list here:
同样,您可以run it live online here。
// By Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html
// =============== Program for determining the default thread stack size =========
#include <alloca.h>
#include <pthread.h>
#include <stdio.h>
void* threadfunc (void*p) {
int n = 0;
for (;;) {
printf("Allocated %d bytes\n", n);
fflush(stdout);
n += 128;
*((volatile char *) alloca(128)) = 0;
}
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, threadfunc, NULL);
for (;;) {}
}
当我使用上面的链接在 GDBOnline 上运行它时,每次运行它都会得到完全相同的结果,无论是 C 还是 C++17 程序。运行大约需要 10 秒左右。以下是输出的最后几行:
Allocated 7449856 bytes
Allocated 7449984 bytes
Allocated 7450112 bytes
Allocated 7450240 bytes
Allocated 7450368 bytes
Allocated 7450496 bytes
Allocated 7450624 bytes
Allocated 7450752 bytes
Allocated 7450880 bytes
Segmentation fault (core dumped)
因此,这个系统的线程堆栈大小约为 7.45 MB,正如上文提到的 Bruno (7.4 MB)。
我对程序进行了一些更改,主要是为了清晰起见,但也是为了提高效率,还有一点是为了学习。
我的更改总结:
-
[学习] 我将 BYTES_TO_ALLOCATE_EACH_LOOP 作为参数传递给 threadfunc(),只是为了练习在 C 中传入和使用通用 void* 参数。
- 注意:这也是传递给
pthread_create() 的回调函数(在我的例子中为threadfunc())所需的函数原型,正如the pthread_create() function 所要求的那样。请参阅:https://www.man7.org/linux/man-pages/man3/pthread_create.3.html。
-
[效率] 我让主线程休眠而不是浪费地旋转。
-
[清晰] 我添加了更详细的变量名称,例如 BYTES_TO_ALLOCATE_EACH_LOOP 和 bytes_allocated。
-
[清晰] 我改变了这个:
*((volatile char *) alloca(128)) = 0;
到这里:
volatile uint8_t * byte_buff =
(volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
byte_buff[0] = 0;
这是我修改后的测试程序,它做的事情和布鲁诺的完全一样,甚至有同样的结果:
您可以run it online here 或download it from my repo here。如果您选择从我的 repo 本地运行它,这里是我用于测试的构建和运行命令:
-
构建并作为 C 程序运行:
mkdir -p bin && \
gcc -Wall -Werror -g3 -O3 -std=c11 -pthread -o bin/tmp \
onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
time bin/tmp
-
构建并作为 C++ 程序运行:
mkdir -p bin && \
g++ -Wall -Werror -g3 -O3 -std=c++17 -pthread -o bin/tmp \
onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
time bin/tmp
在线程堆栈大小约为 7.4 MB 的快速计算机上本地运行需要
这是程序:
// =============== Program for determining the default thread stack size =========
// Modified by Gabriel Staples, 26 Sept. 2020
// Originally by Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html
#include <alloca.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h> // sleep
/// Thread function to repeatedly allocate memory within a thread, printing
/// the total memory allocated each time, until the program crashes. The last
/// value printed before the crash indicates how big a thread's stack size is.
///
/// Note: passing in a `uint32_t` as a `void *` type here is for practice,
/// to learn how to pass in ANY type to a func by using a `void *` parameter.
/// This is also the required function prototype, as required by the
/// `pthread_create()` function, for the callback function (this function)
/// passed to `pthread_create()`. See:
/// https://www.man7.org/linux/man-pages/man3/pthread_create.3.html
void* threadfunc(void* bytes_to_allocate_each_loop)
{
const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP =
*(uint32_t*)bytes_to_allocate_each_loop;
uint32_t bytes_allocated = 0;
while (true)
{
printf("bytes_allocated = %u\n", bytes_allocated);
fflush(stdout);
// NB: it appears that you don't necessarily need `volatile` here,
// but you DO definitely need to actually use (ex: write to) the
// memory allocated by `alloca()`, as we do below, or else the
// `alloca()` call does seem to get optimized out on some systems,
// making this whole program just run infinitely forever without
// ever hitting the expected segmentation fault.
volatile uint8_t * byte_buff =
(volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
byte_buff[0] = 0;
bytes_allocated += BYTES_TO_ALLOCATE_EACH_LOOP;
}
}
int main()
{
const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP = 128;
pthread_t thread;
pthread_create(&thread, NULL, threadfunc,
(void*)(&BYTES_TO_ALLOCATE_EACH_LOOP));
while (true)
{
const unsigned int SLEEP_SEC = 10000;
sleep(SLEEP_SEC);
}
return 0;
}
示例输出(与 Bruno Haible 的原始程序的结果相同):
bytes_allocated = 7450240
bytes_allocated = 7450368
bytes_allocated = 7450496
bytes_allocated = 7450624
bytes_allocated = 7450752
bytes_allocated = 7450880
Segmentation fault (core dumped)