鉴于下面的代码,如果我使用n> 16运行它,我会遇到分段错误。
我认为它与堆栈有关,但我无法弄明白。有人能帮我一把吗?代码不是我的,真的不重要。我希望有人能帮助我了解正在发生的事情。 This SO question非常相似,但是没有足够的信息(发布答案的人会简单地谈论问题,但接着谈论另一种语言)。此外,请注意,有两个演出而且没有递归,我可以(如果我做得对)成功创建超过16000个线程(尽管操作系统只创建大约500个并运行大约300个)。无论如何,我在哪里得到seg故障,为什么?感谢。
#include <pthread.h>
#include <stdio.h>
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;
}
答案 0 :(得分:4)
这是做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生成器,你编写一个程序来生成你可以存储的所有(例如,长值)并写出一个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 <stdio.h>
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 nd 数字那么大 - 它只是一个超过两百万的smidgeon。
所以底线仍然存在:在有意义的地方使用递归(你可以相对快速地减少搜索空间,以便不会耗尽堆栈空间),并在有意义的地方使用threds(你不需要的地方)最终运行这么多,以至于你重载了操作系统的资源。)
答案 1 :(得分:2)
哎呀,不妨回答这个问题。
首先,检查pthread_create
和pthread_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.
事实证明,这种复发的解决方案是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
返回的内容。
答案 2 :(得分:0)
我要做的第一件事是运行printf("%i", PTHREAD_THREADS_MAX);
这样的语句,看看它是什么价值;我不认为操作系统的最大线程必然与pthreads的最大数量相同,虽然我确实看到你说你可以成功实现16000个线程没有递归所以我是只是提到它是我会检查的东西。
应该{{1}}显着&gt;你正在实现的数字线程,我会开始检查pthread_create()调用的返回值,看看你是否得到PTHREAD_THREADS_MAX
。我的怀疑是你的问题的答案是,你试图在一个连接中使用未初始化的线程时遇到了一个段错误...
另外,正如paxdiablo所提到的,你在EAGAIN
处2^16
处的n=16
线程的顺序(假设其中一些在完成最后一个之前完成);我可能会尝试保留一个日志,以查看每个创建的顺序。可能最简单的方法就是使用(n-1)
(n-2)
值作为日志项,否则您将不得不使用信号量或类似信息来保护计数器......
printf可能会陷入困境,事实上,如果通过在新的线程启动之前允许更多线程完成来实际影响事情,我不会感到惊讶,但所以我可能只使用文件write()
进行日志记录。可以只是一个简单的文件,你应该能够通过查看那里的数字模式来了解正在发生的事情。 (等等,假设文件操作是线程安全的;我认为它们是;它已经有一段时间了。)
另外,一旦检查EAGAIN
,您可以尝试睡一会儿并重试;也许它会随着时间的推移而逐渐增加,系统只是被大量的线程请求所淹没,而且由于资源不足而导致其他原因失败;这将验证是否只是等待和重新启动可以让你到达你想要的位置。
最后;我可能会尝试将该函数重新编写为fork()
(我知道fork()是邪恶的或其他;)并看看你是否有更好的运气。
:)