递归调用中pthread的分段错误

鉴于下面的代码,如果我运行n> 16,我会遇到分段错误。

我认为它与堆栈有关,但我无法弄明白。 有人能帮我一把吗? 代码不是我的,真的不重要。 我希望有人能帮助我了解正在发生的事情。 这个问题非常相似,但是没有足够的信息(发布答案的人简要地谈到了这个问题,然后继续谈论另一种语言)。 此外,请注意,有两个演出并且没有递归,我可以(如果我做得对)成功创建超过16000个线程(尽管操作系统仅创建大约500个并且运行大约300个)。 无论如何,我在哪里得到seg故障,为什么? 谢谢。

#include  #include  static void* fibonacci_thread( void* arg ) { int n = (int)arg, fib; pthread_t th1, th2; void* pvalue; /*Holds the value*/ switch (n) { case 0: return (void*)0; case 1: /* Fallthru, Fib(1)=Fib(2)=1 */ case 2: return (void*)1; default: break; } pthread_create(&th1, NULL, fibonacci_thread, (void*)(n-1)); pthread_create( &th2, NULL, fibonacci_thread, (void*)(n-2)); pthread_join(th1, &pvalue); fib = (int)pvalue; pthread_join(th2, &pvalue); fib += (int)pvalue; return (void*)fib; } int main(int argc, char *argv[]) { int n=15; printf ("%d\n",(int)fibonacci_thread((void*)n)); return 0; } 

哎呀,不妨回答这个问题。

首先,检查pthread_createpthread_join的返回值。 (总是,总是,总是检查错误。如果你感到懒惰,只要assert它们会返回零,但永远不要忽略它们。)

其次,我可以发誓Linux glibc默认为每个线程分配2兆字节的堆栈(可通过pthread_attr_setstacksize配置)。 当然,这只是虚拟内存,但在32位系统上仍然限制你总共约2000个线程。

最后,我相信对这将产生的线程数的正确估计基本上是fib(n)本身(如何很好地递归)。 或者大致为phi^n ,其中phi(1+sqrt(5))/2 。 因此,这里的线程数接近2000而不是65000,这与我对32位系统耗尽VM的估计值一致。

[编辑]

要确定系统上新线程的默认堆栈大小,请运行以下程序:

 int main(int argc, char *argv[]) { size_t stacksize; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_getstacksize(&attr, &stacksize); phthread_attr_destroy(&attr); printf("Default stack size = %zd\n", stacksize); return 0; } 

[编辑2]

重复:这远不是2 ^ 16个线程。

设f(n)是计算fib(n)时产生的线程数。

当n = 16时,一个线程产生两个新线程:一个用于计算fib(15),另一个用于计算fib(14)。 所以f(16)= f(15)+ f(14)+ 1。

并且通常f(n)= f(n-1)+ f(n-2)+ 1。

事实certificate,这种复发的解决方案是f(n)只是前n个Fibonacci数的总和:

  1 + 1 + 2 + 3 + 5 + 8 // f(6) + 1 + 1 + 2 + 3 + 5 // + f(5) + 1 // + 1 = 1 + 1 + 2 + 3 + 5 + 8 + 13 // = f(7) 

这是(非常)大致phi^(n+1) ,而不是2^n 。 f(16)的总和仍然以低千,而不是数万来衡量。

[编辑3]

啊,我明白了,问题的症结在于此(从评论中提出):

感谢Nemo的详细解答。 我做了一点测试和pthread_created~10,000个线程,里面只有一个while(1)循环,所以它们不会终止……它确实做到了! 确实,操作系统是聪明的,只能创建大约1000并运行更小的数字,但它没有用完堆栈。 当我生成超过THREAD_MAX的数量时,为什么我没有得到段错误,但是当我递归执行时我会这样做?

这是我的猜测。

你只有几个核心。 在任何时候,内核都必须决定运行哪些线程。 如果你有(比方说)2个核心和500个线程,那么任何特定的线程只会运行1/250的时间。 所以你的主循环产生新线程不会经常运行。 我甚至不确定内核的调度程序对于单个进程中的线程是否“公平”,所以至少可以想象,对于1000个线程,主线程永远不会运行。

至少,每个线程在做while (1); 在放弃时间片之前,它将在其核心上运行1/HZ 。 这可能是1毫秒,但可能高达10毫秒,具体取决于内核的配置方式。 所以,即使调度程序是公平的,当你拥有数千个线程时,你的主线程也只能每秒运行一次。

由于只有主线程正在创建新线程,因此线程创建速度会降低到爬行速度,甚至可能会停止。

试试这个。 而不是while (1); 对于实验中的子线程,尝试while (1) pause(); 。 ( pause来自unistd.h。)这将阻止子线程被阻塞,并且应该允许主线程继续磨掉创建新线程,从而导致崩溃。

再次,请检查pthread_create返回的内容。

不是做Fibonacci序列的好方法:-)

你的第一个线程启动另外两个,每个启动另外两个,依此类推。 因此,当n > 16 ,您可能会得到非常多的线程(数千个) (a)

除非你的CPU拥有比我更多的内核,否则你将浪费时间运行数千个线程来完成这样的CPU绑定任务。 对于纯粹的CPU绑定任务,您最好拥有尽可能多的线程,因为您可以使用物理执行引擎(内核或CPU)。 显然,这些更改不是纯粹受CPU限制的。


如果你想要一个有效的递归(非线程)Fibonacci计算器,你应该使用类似(伪代码)的东西:

 def fib(n, n ranges from 1 to infinity): if n is 1 or 2: return 1 return fib(n-1) + fib(n-2) 

Fibonacci甚至对非线程递归都没有那么好,因为问题不会很快降低。 通过这个,我的意思是计算fib(1000)将使用1000个堆栈帧。 将其与递归二叉树搜索进行比较,其中仅需要十个堆栈帧。 这是因为前者仅为每个堆栈帧移除了1/1000的搜索空间,而后者移除了剩余搜索空间的一半。

做Fibonacci的最好方法是迭代:

 def fib(n, n ranges from 1 to infinity): if n is 1 or 2: return 1 last2 = 1, last1 = 1 for i ranges from 3 to n: last0 = last2 + last1 last2 = last1 last1 = last0 return last0 

当然,如果你想要一个快速快速的Fibonacci生成器,你可以编写一个程序来生成你可以存储的所有数据(例如,一个long值)并写出一个C结构来包含它们。 然后将该输出合并到您的C程序中,您的运行时“计算”将使任何其他方法脱离水中。 这是您标准的“时间折衷空间”优化方法:

 long fib (size_t n) { static long fibs[] = {0, 1, 1, 2, 3, 5, 8, 13, ...}; if (n > sizeof(fibs) / sizeof(*fibs)) return -1; return fibs[n]; } 

这些指南适用于搜索空间不会快速降低的大多数情况(不仅仅是Fibonacci)。


(a)最初,我认为这将是2 16但是,正如下面的程序所示(并且由于Nemo让我直截了当),它并没有那么糟糕 – 我没有考虑到衍生线程的减少性质当你接近fib(0)

 #include  static count = 0; static void fib(int n) { if (n <= 2) return; count++; fib(n-1); count++; fib(n-2); } int main (int argc, char *argv[]) { fib (atoi (argv[1])); printf ("%d\n", count); return 0; } 

这相当于您拥有的代码,但它只是为每个生成的线程增加一个计数器,而不是实际生成它们。 各种输入值的线程数为:

  N Threads --- --------- 1 0 2 0 3 2 4 4 5 8 6 14 : 14 15 1,218 16 1,972 : 20 13,528 : 26 242,784 : 32 4,356,616 

现在请注意,虽然我说它没有那么糟糕,但我并没有说它很好:-)即使是两千个线程也是系统上的公平负载,每个线程都有自己的内核结构和堆栈。 你可以看到,虽然增长开始很小,但它们很快就会加速到无法管理的程度。 而且它并不像第32个数字那么大 - 它只是一个超过两百万的微笑。

所以底线仍然存在:在有意义的地方使用递归(你可以相对快速地减少搜索空间,以便不会耗尽堆栈空间),并在有意义的地方使用threds(你最终不会运行的地方)这么多,你重载操作系统的资源)。

我要做的第一件事就是运行一个像printf("%i", PTHREAD_THREADS_MAX);这样的语句printf("%i", PTHREAD_THREADS_MAX); 看看价值是多少; 我不认为操作系统的最大线程必然与pthread的最大数量相同,虽然我确实看到你说你可以成功实现16000个线程而没有递归,所以我只是提到它作为我想要的东西检查一般。

如果PTHREAD_THREADS_MAX显着>你正在实现的线程数,我会开始检查pthread_create()调用的返回值,看看你是否正在获得EAGAIN 。 我的怀疑是,你的问题的答案是,你在试图在连接中使用未初始化的线程时遇到了一个段错误…

另外,正如paxdiablo所提到的,你在这里讨论的是n=162^16线程的顺序(稍微少一点,假设它们中的一些在最后一个之前完成); 我可能会尝试保留一个日志,以查看每个创建的顺序。 可能最简单的事情就是使用(n-1) (n-2)值作为日志项,否则你将不得不使用信号量或类似的来保护计数器……

printf可能会陷入困境,事实上,如果通过允许更multithreading在新的线程启动之前完成来实际影响事情,我不会感到惊讶,但所以我可能只是使用文件write() ; 可以只是一个简单的文件,你应该能够通过查看那里的数字模式来了解正在发生的事情。 (等等,假设文件操作是线程安全的;我认为它们是;它已经有一段时间了。)

此外,一旦检查EAGAIN您可以尝试睡一会儿并重试; 也许它会随着时间的推移而逐渐增加,系统正在被大量的线程请求所淹没,并且由于某些其他原因而失败,而不是资源不足; 这将validation是否只是等待和重新启动可以让你到达你想要的位置。

最后; 我可能会尝试重新编写函数作为fork() (我知道fork()是邪恶或其他;))看看你是否有更好的运气。

🙂