2

任何想法为什么它适用于像 0、1、2、3、4 ......这样的值以及像 >15 这样的值的段错误?#include #include #include

void *fib(void *fibToFind);

main(){
pthread_t mainthread;

long fibToFind = 15;
long finalFib;

pthread_create(&mainthread,NULL,fib,(void*) fibToFind);

pthread_join(mainthread,(void*)&finalFib);

printf("The number is: %d\n",finalFib);
}


void *fib(void *fibToFind){
long retval;

long newFibToFind = ((long)fibToFind);

long returnMinusOne;
long returnMinustwo;

pthread_t minusone;
pthread_t minustwo;

if(newFibToFind == 0 || newFibToFind == 1)
return newFibToFind;

else{
long newFibToFind1 = ((long)fibToFind) - 1;
long newFibToFind2 = ((long)fibToFind) - 2;

pthread_create(&minusone,NULL,fib,(void*) newFibToFind1);
pthread_create(&minustwo,NULL,fib,(void*) newFibToFind2);

pthread_join(minusone,(void*)&returnMinusOne);
pthread_join(minustwo,(void*)&returnMinustwo);

return returnMinusOne + returnMinustwo;

}

}
4

3 回答 3

3

内存不足(堆栈空间不足)或有效的线程句柄?

您要求大量线程,这需要大量堆栈/上下文。Windows(和Linux)有一个愚蠢的“大[连续]堆栈”想法。

来自 pthreads_create 的文档:“在 Linux/x86-32 上,新线程的默认堆栈大小为 2 兆字节。” 如果您制造 10,000 个线程,则需要 20 Gb 的 RAM。我构建了一个版本的 OP 程序,它在 Windows XP64 上使用了大约 3500 个 (p) 线程。

有关为什么大堆栈是一个非常糟糕的主意的更多详细信息,请参阅此 SO 线程: 为什么堆栈溢出仍然是一个问题?

如果您放弃大堆栈,并为激活记录实现一种具有堆分配的并行语言(我们的PARLANSE就是其中之一),那么问题就消失了。

这是我们在 PARLANSE 中编写的第一个(顺序)程序:

(define fibonacci_argument 45)

(define fibonacci
   (lambda(function natural natural )function 
   `Given n, computes nth fibonacci number'
      (ifthenelse (<= ? 1)
           ?
         (+ (fibonacci (-- ?))
              (fibonacci (- ? 2))
           )+
   )ifthenelse  
   )lambda
 )define

这是在 i7 上运行的执行:

 C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonaccisequential
 Starting Sequential Fibonacci(45)...Runtime: 33.752067 seconds
 Result: 1134903170

这是第二个,它是并行的:

(define coarse_grain_threshold 30) ; technology constant: tune to amortize fork overhead across lots of work

(define parallel_fibonacci
   (lambda (function natural natural )function 
   `Given n, computes nth fibonacci number'
      (ifthenelse (<= ? coarse_grain_threshold)
           (fibonacci ?)
           (let (;; [n natural ] [m natural ]  )
                   (value (|| (= m (parallel_fibonacci (-- ?)) )=
                              (= n (parallel_fibonacci (- ? 2)) )=
                          )||
                          (+ m n)
                   )value
           )let
       )ifthenelse  
   )lambda
)define

明确并行性也使程序更容易编写。

我们通过调用 (parallel_fibonacci 45) 测试的并行版本。这是在同一个 i7 上运行的执行(可以说它有 8 个处理器,但它实际上是 4 个超线程处理器,所以它实际上并不是 8 个等效的 CPU):

C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonacciparallelcoarse
Parallel Coarse-grain Fibonacci(45) with cutoff 30...Runtime: 5.511126 seconds
Result: 1134903170

接近 6+ 的加速,对于非 8 处理器来说还不错。这个问题的其他答案之一是运行 pthreads 版本。计算 Fib(18) 需要“几秒钟”(炸毁),而 Fib(45) 是 5.5 秒。这告诉你 pthreads 是一种根本上做大量细粒度并行的坏方法,因为它有非常非常高的分叉开销。(PARLANSE 旨在最大限度地减少分叉开销)。

如果将技术常数设置为零(每次调用 fib 时分叉),会发生以下情况:

C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonacciparallel
Starting Parallel Fibonacci(45)...Runtime: 15.578779 seconds
Result: 1134903170

你可以看到分摊分叉开销是一个好主意,即使你有快速分叉。

Fib(45) 产生大量谷物。激活记录的堆分配解决了 OP 的一阶问题(每个具有 1Mb 堆栈的数千个 pthread 会消耗 GB 的 RAM)。

但是还有第二个问题:即使您的谷物控制块很小,2 ^ 45 PARLANSE“谷物”也会燃烧您的所有记忆,只是跟踪谷物。因此,一旦你有“很多”(对于“很多”的某些定义,明显少于 2^45)颗粒,它会有助于有一个调度程序来限制分叉,以防止并行性爆炸用“颗粒”跟踪数据淹没机器结构。当grain的数量也低于阈值时,它必须解除对分叉的限制,以确保物理CPU总是有大量的逻辑并行工作要做。

于 2011-02-23T01:29:46.693 回答
0

您没有检查错误 - 特别是来自pthread_create(). pthread_create()失败时,变量pthread_t未定义,后续pthread_join()可能会崩溃。

如果你确实检查错误,你会发现它pthread_create()失败了。这是因为您正在尝试生成近 2000 个线程 - 使用默认设置,这将需要单独分配 16GB 的线程堆栈。

您应该修改您的算法,使其不会生成这么多线程。

于 2011-02-23T01:36:12.273 回答
0

我试图运行你的代码,遇到了几个惊喜:

printf("The number is: %d\n", finalFib);

此行有一个小错误:%d表示printf需要一个int,但传递了一个long int。在大多数平台上这是相同的,或者无论如何都会有相同的行为,但是从迂腐的角度来说(或者如果你只是想阻止警告出现,这也是一个非常崇高的理想),你应该使用%ld它,这将期望一个long int

fib另一方面,您的功能似乎没有功能。在我的机器上测试它,它不会崩溃,但它会产生1047,这不是斐波那契数。仔细观察,您的程序似乎在几个方面不正确:

void *fib(void *fibToFind)
{
    long retval; // retval is never used

    long newFibToFind = ((long)fibToFind);

    long returnMinusOne; // variable is read but never initialized
    long returnMinustwo; // variable is read but never initialized

    pthread_t minusone; // variable is never used (?)
    pthread_t minustwo; // variable is never used

    if(newFibToFind == 0 || newFibToFind == 1)
        // you miss a cast here (but you really shouldn't do it this way)
        return newFibToFind;        
    else{
        long newFibToFind1 = ((long)fibToFind) - 1; // variable is never used
        long newFibToFind2 = ((long)fibToFind) - 2; // variable is never used
        // reading undefined variables (and missing a cast)
        return returnMinusOne + returnMinustwo;

    }
}

始终注意编译器警告:通常,当您收到警告时,您确实在做一些可疑的事情。

也许你应该稍微修改一下算法:现在,你的函数所做的只是返回两个未定义值的总和,因此我之前得到的 1047。

使用递归算法实现斐波那契套件意味着您需要再次调用该函数。正如其他人所指出的,这是一种非常低效的方法,但它很容易,所以我想所有的计算机科学教师都以它为例。

常规递归算法如下所示:

int fibonacci(int iteration)
{
    if (iteration == 0 || iteration == 1)
        return 1;

    return fibonacci(iteration - 1) + fibonacci(iteration - 2);
}

我不知道你应该在多大程度上使用线程——只是在辅助线程上运行算法,或者为每个调用创建新线程?现在让我们假设第一个,因为它更简单。

将整数转换为指针,反之亦然,这是一种不好的做法,因为如果您尝试在更高的层次上看待事物,它们应该会有很大的不同。整数做数学运算,指针解析内存地址。它恰好起作用,因为它们以相同的方式表示,但实际上,你不应该这样做。相反,您可能会注意到为运行新线程而调用的函数接受了一个void*参数:我们可以使用它来传达输入的位置和输出的位置

因此,在我之前的fibonacci函数的基础上,您可以将此代码用作线程主例程:

void* fibonacci_offshored(void* pointer)
{
    int* pointer_to_number = pointer;
    int input = *pointer_to_number;
    *pointer_to_number = fibonacci(input);
    return NULL;
}

它需要一个指向整数的指针,并从中获取输入,然后将其写入输出。1然后,您将像这样创建线程:

int main()
{
    int value = 15;
    pthread_t thread;

    // on input, value should contain the number of iterations;
    // after the end of the function, it will contain the result of
    //  the fibonacci function
    int result = pthread_create(&thread, NULL, fibonacci_offshored, &value);

    // error checking is important! try to crash gracefully at the very least
    if (result != 0)
    {
        perror("pthread_create");
        return 1;
    }

    if (pthread_join(thread, NULL)
    {
        perror("pthread_join");
        return 1;
    }

    // now, value contains the output of the fibonacci function
    // (note that value is an int, so just %d is fine)
    printf("The value is %d\n", value);
    return 0;
}

如果您需要从新的不同线程调用 Fibonacci 函数(请注意:这不是我的建议,其他人似乎同意我的观点;它只会在足够多的迭代中爆炸),您将首先需要将fibonacci函数与fibonacci_offshored函数合并。它会大大增加它的体积,因为处理线程比处理常规函数更重。

void* threaded_fibonacci(void* pointer)
{
    int* pointer_to_number = pointer;
    int input = *pointer_to_number;

    if (input == 0 || input == 1)
    {
        *pointer_to_number = 1;
        return NULL;
    }

    // we need one argument per thread
    int minus_one_number = input - 1;
    int minus_two_number = input - 2;

    pthread_t minus_one;
    pthread_t minus_two;
    // don't forget to check! especially that in a recursive function where the
    // recursion set actually grows instead of shrinking, you're bound to fail
    // at some point
    if (pthread_create(&minus_one, NULL, threaded_fibonacci, &minus_one_number) != 0)
    {
        perror("pthread_create");
        *pointer_to_number = 0;
        return NULL;
    }
    if (pthread_create(&minus_two, NULL, threaded_fibonacci, &minus_two_number) != 0)
    {
        perror("pthread_create");
        *pointer_to_number = 0;
        return NULL;
    }

    if (pthread_join(minus_one, NULL) != 0)
    {
        perror("pthread_join");
        *pointer_to_number = 0;
        return NULL;
    }

    if (pthread_join(minus_two, NULL) != 0)
    {
        perror("pthread_join");
        *pointer_to_number = 0;
        return NULL;
    }

    *pointer_to_number = minus_one_number + minus_two_number;
    return NULL;
}

现在您有了这个庞大的函数,对main函数的调整将非常容易:只需更改对 的引用fibonacci_offshored即可threaded_fibonacci

int main()
{
    int value = 15;
    pthread_t thread;

    int result = pthread_create(&thread, NULL, threaded_fibonacci, &value);

    if (result != 0)
    {
        perror("pthread_create");
        return 1;
    }
    pthread_join(thread, NULL);

    printf("The value is %d\n", value);
    return 0;
}

您可能被告知线程可以加速并行进程,但是在某个地方设置线程比运行其内容更昂贵是有限制的。这是这种情况的一个很好的例子:程序的线程版本比非线程版本运行得慢得多。

出于教育目的,当所需的迭代次数为 18 时,该程序在我的机器上用完了线程,并且需要几秒钟才能运行。相比之下,使用迭代实现,我们永远不会用完线程,而且我们会在几毫秒内得到答案。它也相当简单。这将是一个很好的例子,说明如何使用更好的算法解决许多问题。

此外,出于好奇,看看它是否在您的机器上崩溃以及在哪里/如何崩溃会很有趣。

1. 通常应尽量避免在输入时的值和函数返回后的值之间改变变量的含义。例如,在这里,输入时,变量是我们想要的迭代次数;在输出上,它是函数的结果。这是两个非常不同的含义,这不是一个很好的做法。我不想使用动态分配通过void*返回值返回值。

于 2011-02-23T04:58:50.477 回答