在线程中计算圆周率

6

我有两个使用蒙特卡洛方法计算π的实现:一个有线程,一个没有。没有使用线程的实现可以正常工作,但是使用线程的方法在准确性和性能方面存在问题。下面是代码:

没有使用线程:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int main()
{
    srand(time(NULL));

    unsigned long N = 0, Nin = 0;
    float x,y;

    while(N < 2E+9)
    {
        x = rand()/((float)RAND_MAX + 1.0)*10.0 - 5.0;
        y = rand()/((float)RAND_MAX + 1.0)*10.0 - 5.0;

        if(x*x + y*y < 25.0) Nin += 1;
        N++;
    }
    long double pi = 4.0 * (long double)Nin / (long double)N;

    printf("\tPi1: %.20Lf\n\t%lu %lu\n", pi, Nin, N);

    return 0;
}

并且使用线程:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>


typedef struct 
{
    unsigned long Nin;
    unsigned long N;
} nums;


void pi_counter(nums* a)
{
    float x,y;
    unsigned int N = 0, Nin = 0;

    while(N < 1E+9)
    {
        x = rand()/((float)RAND_MAX + 1.0)*10.0 - 5.0;
        y = rand()/((float)RAND_MAX + 1.0)*10.0 - 5.0;


        if(x*x + y*y < 25.0) Nin++;
        N++;
    }

    a -> Nin += Nin;
    a -> N   += N;
}


int main()
{
    pthread_t thread1, thread2, thread3;
    nums a;

    srand(time(NULL));

    pthread_create( &thread1, NULL, pi_counter, &a );
    pthread_create( &thread2, NULL, pi_counter, &a );

    pthread_join( thread1, NULL );
    pthread_join( thread2, NULL ); 

    long double pi = 4.0 * (long double)a.Nin / (long double)a.N;


    printf("\tPi2: %.20Lf\n\t%lu %lu\n", pi, a.Nin, a.N);

    return 0;
}

结果:

$ time ./pi2
    Pi2: 3.14147154999999999995
    1570735775 2000000000

real    1m1.927s
user    1m23.624s
sys 0m0.139s



$ time ./pi
    Pi1: 3.14158868600000000006
    1570794343 2000000000

real    0m49.956s
user    0m49.887s
sys 0m0.022s

我的错误在哪里?

3个回答

11

rand 不是线程安全的;在多个线程中同时使用它会导致未定义行为。你可以将其包装在一个函数中,在调用 rand 时获取并持有互斥锁,或者你可以使用 rand_r 或(更好的选择)编写一个适当的 PRNG 以替代它。


“未定义行为”?听起来很随意啊 ;) - Adrian Petrescu
2
...或者erand48(),它直接生成一个在范围 [0.0, 1.0) 内的 double 类型,并且在 POSIX 中没有被标注为废弃。 - caf

2
除了其他的答案,以下代码中
a -> Nin += Nin;
a -> N   += N;

a是共享的,但没有被互斥锁保护,会导致错误的加法。虽然你可能没有遇到过这个问题,但最终你会遇到。


1

你使用线程并发地调用rand()函数,会导致结果相同的数列。这是因为虽然每次执行结果不同,但是算法本身没有问题(是概率算法,无法保证每次结果都不同)。为什么会出现相同的数列呢?因为每个进程拥有一个独立的随机数种子,而线程也是一种轻量级的进程。


实际情况更糟:它会引发未定义行为。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接