递归调用中pthread的分段错误

时间:2011-07-12 03:17:06

标签: c stack pthreads segmentation-fault

鉴于下面的代码,如果我使用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;
}

3 个答案:

答案 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_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.

事实证明,这种复发的解决方案是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所提到的,你在EAGAIN2^16处的n=16线程的顺序(假设其中一些在完成最后一个之前完成);我可能会尝试保留一个日志,以查看每个创建的顺序。可能最简单的方法就是使用(n-1) (n-2)值作为日志项,否则您将不得不使用信号量或类似信息来保护计数器......

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

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

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

:)