#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()
, который непосредственно генерирует adouble
в диапазоне[0.0, 1.0)
и не помечается как устаревший в POSIX.
Ответ №2:
Помимо других ответов, в следующем коде
a -> Nin = Nin;
a -> N = N;
a является общим, но не защищен мьютексом, что приводит к неправильному добавлению. Хотя вы, возможно, и не сталкивались с этой проблемой, но рано или поздно столкнетесь.
Ответ №3:
Ваше rand()
одновременное выполнение в потоках приведет к той же последовательности чисел, потому что вы получите другие результаты, но алгоритм в порядке (является вероятностным, ничего не гарантируется). Почему в той же последовательности? Поскольку начальный экземпляр rand предоставляется для каждого процесса, поток является отростчатым, но легковесным.
Комментарии:
1. На самом деле ситуация хуже: он вызывает UB.