mardi 2 mars 2021

Writing to same pointer from two different threads in C with pthreads

I am trying to Monte Carlo simulate an approximation of pi, by placing random points in a unit square, and seeing which points fall inside the unit circle. I am asked to do this with threads using pthreads. I am familiar with CUDA programming to some extent, so I am a bit familiar with some of the pitfalls. I seem to have a working program that runs faster than the sequential single-threaded program. However, I have two questions, that I believe pertains to my understanding of parallelizing the code.

  1. At first I passed a single pointer to the variable int* ptsInCircle to both threads. I had a slight suspicion that this might not work, since both threads try to simultaneously write to the same block of memory. It did not work at all, and I had to implement it as below, using two separate pointers, and join the results at the end. Can someone explain to me why this is the case? I am not sure I understand. One thing I did notice, was that if I printed the value of ptsInCircle within a loop, it seemed as though the variable was correctly updated. Is this due to each thread having to spend more time each iteration, allowing the other thread to access the address without conflicting with each other?
  2. I seem to, even using int numOfPts = 1e8 as an argument to the function, have slight oscillations of the value between concurrent executions of the binary. Why is this?
  3. Also any tips and points on the structure of my program, or parallelizing this case in particular are greatly appreciated. Thanks in advance.

Here is my code:

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

#include "piApproxMultithreaded.h"

typedef struct placePtsArgStruct {
    unsigned int* seed;
    double startingPt;
    double numOfPts;
    int* ptsInCircle;
    double unitCircleRadius;
} placePtsArgStruct;


double randomNumber( unsigned int *seed ){
  /*_Thread_local*/
  double maxRand      =   (double)RAND_MAX;           // Maximum random number, cast to double
  double randNum      =   (double)rand_r( seed );     // Generate pseudo-random number from seed, cast to double

  return 2 * randNum/maxRand - 1;                     // Recast number between -1 and 1
}

void* placePts( void* placePtsArgStructInput ){
  placePtsArgStruct* args   =   ( placePtsArgStruct* )placePtsArgStructInput;

  unsigned int* seed        =   args->seed              ;
  double startingPt         =   args->startingPt        ;
  double numOfPts           =   args->numOfPts          ;
  int* ptsInCircle          =   args->ptsInCircle       ;
  double unitCircleRadius   =   args->unitCircleRadius  ;

  double xpos   =   0;
  double ypos   =   0;

  for ( int iteration = startingPt; iteration < numOfPts; iteration++ ){
    xpos = randomNumber(seed);
    ypos = randomNumber(seed);

    if ( sqrt( pow(xpos, 2) + pow(ypos, 2) ) <= unitCircleRadius ){
      (*ptsInCircle)++;
      //printf("%d\n", *ptsInCircle);
    }
  }

  return NULL;
}


void piApproxMultithreaded( int numOfPts ){
  unsigned int seed         =   time(NULL) ;
  double unitCircleRadius   =   1.0        ;

  int* ptsInCircle_first = malloc(sizeof(int));
  *ptsInCircle_first = 0;
  int* ptsInCircle_second = malloc(sizeof(int));
  *ptsInCircle_second = 0;

  int startingPt_firstPart    =   0;
  int startingPt_secondPart   =   (int)(((double)numOfPts)/2.0);
  int numOfPts_firstPart      =   startingPt_secondPart;
  int numOfPts_secondPart     =   numOfPts;

  placePtsArgStruct placePtsArgStruct_firstPart   =  { &seed, startingPt_firstPart , numOfPts_firstPart , ptsInCircle_first, unitCircleRadius };
  placePtsArgStruct placePtsArgStruct_secondPart  =  { &seed, startingPt_secondPart, numOfPts_secondPart, ptsInCircle_second, unitCircleRadius };

  pthread_t firstPart;
  pthread_t secondPart;

  pthread_attr_t* attributes = NULL;
  pthread_create( &firstPart,  attributes, placePts, (void*)&placePtsArgStruct_firstPart  );
  pthread_create( &secondPart, attributes, placePts, (void*)&placePtsArgStruct_secondPart );

  void* status = NULL;
  pthread_join(firstPart,  status);
    pthread_join(secondPart, status);

  double ptsInCircle  = *ptsInCircle_first + *ptsInCircle_second;

  double myPiApprox   =   4.0*ptsInCircle/((double)numOfPts);

  printf("My approximation of pi = %g\n"  , myPiApprox                    );
  printf("Pi is actually = %g\n"          , M_PI                          );
  printf("Deviation is %g percent\n"      , fabs(1.0-myPiApprox/M_PI)*100 );
}

I am of course calling the program from a seperate file main.c:

#include "piApproxMultithreaded.h"
#include <pthread.h>

int main( void ){

  int numOfPts = 1e9;
  piApproxMultithreaded( numOfPts );

  return 0;
}

Lastly, I have the header file piApproxMultithreaded.h

#ifndef HAVE_PIAPPROX_H
#define HAVE_PIAPPROX_H

#include <pthread.h>

void piApproxMultithreaded( int numOfPts );

#endif



Aucun commentaire:

Enregistrer un commentaire