We have several tasks which can be executed in parallel. Is it possible to run each task in its own thread?

We already know that this is possible with the C++11 standard library. If we have two functions

void function_1();

void function_2();

we can run them in their own threads:

std::thread thread_1(function_1);
std::thread thread_2(function_2);
    
thread_1.join();
thread_2.join();

Is something similar possible with OpenMP? The answer is: yes.

Sections

The section construct is one way to distribute different tasks to different threads. The syntax of this construct is:

#pragma omp parallel 
{
    #pragma omp sections
    {
        #pragma omp section
        {
            // structured block 1
        }
        
        #pragma omp section
        {
            // structured block 2
        }

        #pragma omp section
        {
            // structured block 3
        }           
        
        ...
    }
}

The parallel construct creates a team of threads which execute in parallel.

The sections construct indicates the start of the construct. It contains several section constructs. Each section marks the different block, which represents a task. Be aware, the use of singular and plural nouns (section / sections) is important!

The sections construct distributes the blocks/tasks between existing threads. The requirement is that each block must be independent of the other blocks. Then each thread executes one block at a time. Each block is executed only once by one thread.

There are no assumptions about the order of the execution. The distribution of the tasks to the threads is implementation defined. If there are more tasks than threads, some of the threads will execute multiple blocks. If there are more threads than tasks, some of the threads will be idle.

If there is only one sections construct inside the parallel construct

#pragma omp parallel
{
    #pragma omp sections
    {
        ...
    }
}

we can simplify both constructs into the combined construct

#pragma omp parallel sections
{
    ...
}

The documentation for the sections construct is available in the OpenMP specification on the page 65.

Example

Let us present a simple example.

We have two functions

void function_1()
{
    for (int i = 0; i != 3; i++)
    {
        std::cout << "Function 1 (i = " << i << ")" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void function_2()
{
    for (int j = 0; j != 4; j++)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        std::cout << "                   Function 2 "
                  << "(j = " << j << ")" << std::endl;
    }
}

which print some information. We use the sleep_for function to delay the printing between the iterations.

The main function is

int main()
{
    #pragma omp parallel sections
    {    
        #pragma omp section
        function_1();
            
        #pragma omp section
        function_2();
    }

    return 0;
}

At the beginning, the combined construct creates a team of threads. The OpenMP distributes the function calls function_1(); and function_2(); between the available threads.

If the number of threads is higher than one, then the program executes the function calls in parallel. If the number of threads is equal to one, than the program executes the function calls sequentially.

The timeline of the program is presented in the picture (if the number of threads is higher than one).

Sections

The output of the program is

$ ./ompSection 
Function 1 (i = 0)
                   Function 2 (j = 0)
Function 1 (i = 1)
                   Function 2 (j = 1)
                   Function 2 (j = 2)
Function 1 (i = 2)
                   Function 2 (j = 3)

We can clearly see that the functions are executed in parallel.

We can also run the program with only one thread. In this case, the output is

$ export OMP_NUM_THREADS=1
$ ./ompSection 
Function 1 (i = 0)
Function 1 (i = 1)
Function 1 (i = 2)
                   Function 2 (j = 0)
                   Function 2 (j = 1)
                   Function 2 (j = 2)
                   Function 2 (j = 3)

Summary

In this article, we learned how to run several tasks in parallel with the OpenMP API.

Links: