Creating Parallel Do Loops with Threading Building Blocks Share your comment!

Sometimes when you’re building an algorithm, a do-while loop is more suitable than a for loop. In traditional C++ programming, you have constructs besides the for loop, but if you want to use a for loop, you can use it to mimic a do-while loop like so:

1
bool done = false;
2
int x = 0;
3
for (; done == false ; ) {
4
    std::cout << x++ << std::endl;
5
    if (x == 20) {
6
        done = true;
7
    }
8
}

Unfortunately, the cilk_for loop doesn’t allow for this. If you change the for keyword to cilk_for, you’ll get a compile-time error:

1
error : missing control variable declaration and initialization in _Cilk_for

The cilk_for keyword has much tighter requirements than the regular for keyword. (For example, you also can’t use a break statement inside a cilk_for loop, nor can you modify the index variable inside a cilk_for loop.)

The solution, then, is to use a while loop. This is where the threading building blocks library comes in, and its parallel_do algorithm.

To understand parallel_do, it’s important to understand how algorithms are implemented in C++. Let’s take a look at some code from the examples that come with Intel Parallel Studio. (If you used the default installation directories, you can find this code in C:\Program Files (x86)\Intel\Parallel Studio 2011\Composer\tbb\examples\parallel_do\parallel_preorder\parallel_preorder.cpp.)

The call to the parallel_for looks like this:

1
tbb::parallel_do(root_set.begin(), root_set.end(),Body());

parallel_do is a template function declared inside the parallel_do.h header file. In order to provide the code that runs inside the loop, you create your own class and give it an operator function called simply “()” (that is, open parenthesis, close parenthesis). And inside that function is the code that will run in parallel. This is a common approach in advanced C++ programming that makes use of templates functions for algorithms, whereby you place your code in a member function, and pass a new instance of your class as a parameter to the algorithm function. But to avoid the necessity of having to base your class from a common abstract class, the algorithm is designed as a template function. Then inside the template code is a call to your provided operator function. Your Body class needs to look like this:

1
class Body {
2
public:
3
    MyBody() {};
4
    typedef Cell* argument_type;
5
    void operator()( /* params */ ) const {
6
        // Your function code goes here
7
    }
8
};   

You place your function code inside where I put the comment. As for the parameters in the operator function, we need to first look at the first two parameters to the parallel_do loop. These first two parameters are C++ iterators. Technically these are again just template parameters, and as such you don’t need to use the built-in C++ iterators. But the cool thing is that the parallel_for works beautifully with the built-in iterators. That means you can use code like this:

1
std::list<MyClass> mylist;

and then pass the mylist’s iterators into your parallel_for loop:

1
tbb::parallel_do(mylist.begin(), mylist.end(), Body());

When you do this, the parameter in your operator function is simple: It’s the next object in the list, like so:

1
void operator()(MyClass& myitem) const {
2
    myitem.doSomething();
3
}

And again, because we’re using templates, you can access any member of the class that’s stored in the list. In this line, I’m calling a doSomething() function, which is a member of my hypothetical MyClass class.

Note also that you’re not required to use the class name Body (which is one of the points of templates). So if you have multiple loops, you can create a new class for each one, just giving it a different name. (You can’t, however, pack your functions all into a single Body class.) And fortunately, if you make a mistake in your class and misname your operator function, the compiler is smart enough (unlike older compilers that used to give bizarre errors to template problems). You’ll simply see the message:

1
'()' : is not a member of 'Body'

Below is a complete example, with MyClass instances being put into a list, and the MyClass class holding the loop code.

01
class MyClass {
02
public:
03
    MyClass(int _x) : x(_x) { }
04
    int x;
05
};
06
 
07
class MyLoop {
08
public:
09
    void operator()(MyClass& myitem) const {
10
        std::cout << myitem.x << std::endl;
11
    }
12
};
13
 
14
void demo() {
15
    // Build the list
16
    std::list<MyClass> mylist;
17
    for (int i=0; i<10; i++) {
18
        mylist.push_back(MyClass(i));
19
    }
20
 
21
    // Do the parallel loop
22
    tbb::parallel_do(mylist.begin(), mylist.end(), MyLoop());
23
}

Now it’s your turn…

Have you tried any of the other algorithms in Parallel Building Blocks? 

Share your experiences in the comments section below. I’m eager to hear what you’ve come up with.

________________________________________________________________

Jeff Cogswell is a Geeknet contributing editor, and is the author of several tech books including C++ All-In-One Desk Reference For Dummies, C++ Cookbook, and Designing Highly Useable Software. A software engineer for over 20 years, Jeff has written extensively on many different development topics. An expert in C++ and JavaScript, he has experience starting from low-level C development on Linux, up through modern web development in JavaScript and jQuery, PHP, and ASP.NET MVC. 

Posted on by Jeff Cogswell, Geeknet Contributing Editor
0 comments