Подсчет числа Pi в потоках

#c #pthreads #pi

#c #pthreads #число pi

Вопрос:

У меня есть две реализации подсчета числа pi с помощью метода Монте-Карло: с потоками и без них. Реализация без потоков работает просто отлично, но метод с потоками имеет проблемы с точностью и производительностью. Вот код:

Без потоков:

 #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: %.20Lfnt%lu %lun", 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( amp;thread1, NULL, pi_counter, amp;a );
    pthread_create( amp;thread2, NULL, pi_counter, amp;a );

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

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


    printf("tPi2: %.20Lfnt%lu %lun", 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
  

В чем моя ошибка?

Ответ №1:

rand не является потокобезопасным; одновременное использование его в нескольких потоках приведет к неопределенному поведению. Вы можете либо обернуть это функцией, которая получает и удерживает мьютекс во время вызова rand , либо вы можете использовать rand_r или (что еще лучше) написать достойный PRNG для использования вместо него.

Комментарии:

1. «Неопределенное поведение»? Звучит красиво… случайный … для меня 😉

2. ..или erand48() , который непосредственно генерирует a double в диапазоне [0.0, 1.0) и не помечается как устаревший в POSIX.

Ответ №2:

Помимо других ответов, в следующем коде

 a -> Nin  = Nin;
a -> N    = N;
  

a является общим, но не защищен мьютексом, что приводит к неправильному добавлению. Хотя вы, возможно, и не сталкивались с этой проблемой, но рано или поздно столкнетесь.

Ответ №3:

Ваше rand() одновременное выполнение в потоках приведет к той же последовательности чисел, потому что вы получите другие результаты, но алгоритм в порядке (является вероятностным, ничего не гарантируется). Почему в той же последовательности? Поскольку начальный экземпляр rand предоставляется для каждого процесса, поток является отростчатым, но легковесным.

Комментарии:

1. На самом деле ситуация хуже: он вызывает UB.