Search code examples
cmultithreadingopenmp

Which value does e take here?


I have the following piece of code (note: this is to help me understand the concept so it is an example code not something I intend to run)

List ml; //my_list
Element *e;
#pragma omp parallel
#pragma omp single
{
    for(e=ml->first;e;e=e->next)
    #pragma omp task 
    process(e);//random function
}

It was mentioned that e will always have the same value, I am trying to think why is that and what the value will be.

My try/reasoning:

The value of e is changing inside pragma omp single, these changes don't get carried out inside task (If I am not mistaken no value of the single region get carried to inside the task unless I use something like firstprivate(e), moreover, the value it will take will be random since we didn't initialize e to any variable outside the single and parallel omp region and that what will e take as a value, if we had initialized outside to a value x for example then e will always be x

Any help correcting or verifying my reasoning would be appreciated.


Solution

  • I have added some comments to your example to hopefully make the behavior a bit clearer.

    List ml; //my_list
    Element *e;
    
    #pragma omp parallel  // e is shared for the parallel region
    #pragma omp single
    {
       for(e=ml->first;e;e=e->next)
         #pragma omp task  // since e is shared, all tasks will see the "same" e
         process(e);
    }
    

    What happens is this (indicated by the comments above): you're declaring e outside of the scope of the parallel constructs. As per the OpenMP specification, the variable will be shared across all the threads executing. The single constructs then restricts execution to any one thread of the team (e is still shared across all the threads, see https://www.openmp.org/spec-html/5.1/openmpsu113.html#x148-1600002.21.1).

    When the picked thread encounters the task construct, the OpenMP specification mandates that the created task from the task inherits the sharing attributes of the e variable (shared), so all created tasks will see the same variable and the picked thread may overwrite the e variable while it executes the for loop.

    That's where the firstprivate(e) comes in:

    List ml; //my_list
    Element *e;
    
    #pragma omp parallel  // e is shared for the parallel region
    #pragma omp single
    {
      for(e=ml->first;e;e=e->next)
      #pragma omp task firstprivate(e) // task now receives a private "copy" of e
        process(e);
    }
    

    Here, the create tasks will have a private copy of e that is initialized with the current value of e as the picked thread progresses through the for loop.

    Another way to fix this would be this:

    List ml; //my_list
    
    #pragma omp parallel  
    #pragma omp single
    {
      Element *e;   // e is thread-private inside the parallel region
      for(e=ml->first;e;e=e->next)
      #pragma omp task // task now receives a private "copy" of e w/o firstprivate
        process(e);
    }
    

    Since in this example, the OpenMP specification mandates that the variable should be treated as if you specified firstprivate(e) (see https://www.openmp.org/spec-html/5.1/openmpsu113.html#x148-1610002.21.1.1).