#include <stdio.h>

#ifdef _OPENMP
    #include <omp.h>
#endif

int threadprivatevar;
#pragma omp threadprivate(threadprivatevar)

int main(int argc, char *argv[]) {
    #ifdef _OPENMP

        /* turn off dynamic threads
           threadprivate variables are only guaranteed to persist without dynamic threads */
        omp_set_dynamic(0);

        printf("Variable scope attribute reduction:\n");
        int reductionvar = 0;
        #pragma omp parallel reduction(+: reductionvar)
        {
          reductionvar++;
          #pragma omp critical
          printf("%i/%i reductionvar = %i\n", omp_get_thread_num(), omp_get_num_threads(), reductionvar);
        }
        printf("%i/%i reductionvar = %i\n", omp_get_thread_num(), omp_get_num_threads(), reductionvar);

/*

Variable scope attribute reduction:
0/2 reductionvar = 1
1/2 reductionvar = 1
0/1 reductionvar = 2

*/

        printf("Variable scope attribute private:\n");
        int privatevar = 9;
        printf("%i/%i privatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), privatevar);
        #pragma omp parallel private(privatevar)
        {
          #pragma omp critical
          printf("%i/%i privatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), privatevar);
        }
        printf("%i/%i privatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), privatevar);
       
/*

Variable scope attribute private:
0/1 privatevar = 9
1/2 privatevar = 0
0/2 privatevar = 0
0/1 privatevar = 9

*/

        printf("Variable scope attribute firstprivate:\n");
        int firstprivatevar = 0;
	printf("%i/%i firstprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), firstprivatevar);
        #pragma omp parallel firstprivate(firstprivatevar)
        {
          firstprivatevar++;
          #pragma omp critical
          printf("%i/%i firstprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), firstprivatevar);
        }
        printf("%i/%i firstprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), firstprivatevar);

/*

Variable scope attribute firstprivate:
0/1 firstprivatevar = 0
1/2 firstprivatevar = 1
0/2 firstprivatevar = 1
0/1 firstprivatevar = 0

*/
        printf("Variable scope attribute lastprivate:\n");
        int lastprivatevar = 0;
	printf("%i/%i lastprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), lastprivatevar);
        #pragma omp parallel sections lastprivate(lastprivatevar)
        {
          #pragma omp section
          {
            lastprivatevar = 1;
            #pragma omp critical
            printf("%i/%i lastprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), lastprivatevar);
          }
          #pragma omp section
          {
            lastprivatevar = 2;
            #pragma omp critical
            printf("%i/%i lastprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), lastprivatevar);
          }
        }
        printf("%i/%i lastprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), lastprivatevar);

/*

Variable scope attribute lastprivate:
0/1 lastprivatevar = 0
0/2 lastprivatevar = 1
1/2 lastprivatevar = 2
0/1 lastprivatevar = 2

*/

        printf("Variable scope attribute threadprivate:\n");
        threadprivatevar = 0;
        #pragma omp parallel copyin(threadprivatevar)
        {
          #pragma omp critical
	  printf("%i/%i copyin value: threadprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), threadprivatevar);
          threadprivatevar = omp_get_thread_num();
          #pragma omp critical
	  printf("%i/%i threadprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), threadprivatevar);
        }
        #pragma omp parallel
        {
          #pragma omp critical
	  printf("%i/%i next parallel region: threadprivatevar = %i\n", omp_get_thread_num(), omp_get_num_threads(), threadprivatevar);
        }

/*

Variable scope attribute threadprivate:
1/2 copyin value: threadprivatevar = 0
1/2 threadprivatevar = 1
0/2 copyin value: threadprivatevar = 0
0/2 threadprivatevar = 0
1/2 next parallel region: threadprivatevar = 1
0/2 next parallel region: threadprivatevar = 0

*/

        printf("Variable scope attribute shared:\n");
        int sharedvar = 0;
	printf("%i/%i sharedvar = %i\n", omp_get_thread_num(), omp_get_num_threads(), sharedvar);
        #pragma omp parallel shared(sharedvar)
        {
          if (omp_get_thread_num() == 0) {
	    int i;
            for (i = 0; i < 1000000; i++)
              sharedvar=0;
          }
          else {
	    int i;
            for (i = 0; i < 1000000; i++)
              sharedvar++;
          }
          #pragma omp critical
	  printf("%i/%i uncoordinated access to sharedvar = %i\n", omp_get_thread_num(), omp_get_num_threads(), sharedvar);
        }
	printf("%i/%i uncoordinated access to sharedvar = %i\n", omp_get_thread_num(), omp_get_num_threads(), sharedvar);

/*

Variable scope attribute shared:
0/1 sharedvar = 0
1/2 uncoordinated access to sharedvar = 0
0/2 uncoordinated access to sharedvar = 0
0/1 uncoordinated access to sharedvar = 0

*/

    #else
        printf("No OpenMP support\n");
    #endif

    return 0;
}