Creating Starter and Continuing Graph Nodes with Threading Building Blocks Share your comment!

Last week we talked about the general concepts behind the graph mechanism in Threading Building Blocks (TBB).    This week I want to show you some code. We’ll spend the next couple of weeks working on this, because there’s more than we can fit into a single blog. Let’s start with the basic concepts.

Important note: Before you can use the graph types, you need to get the latest build of TBB, which you can find here

Second important note: The graph types live in a sub-namespace within the tbb namespace. The easiest way to access the types is with a using statement:

using namespace tbb::graph;

Next, we need to create a graph. This one is easy:

graph g;

After we have our graph, we need a starting node. Remember, a node can be a receiver, a sender, or both. A sender sends a message to a subsequent node (the receiver) to start its processing. If a node is in the middle of a chain, it can be both a sender and a receiver. It waits until its predecessor sends it the message to begin doing its work. Then it will send a message to its subsequent nodes. Thus it both receives and sends.

The starting node doesn’t need to be a receiver, but it won’t hurt if it is, provided we manually start it out. One class that works for this is found in TBB and is called broadcast_node. This is a node, and a sender. It’s also a receiver, but we won’t be using that aspect of it. Now remember, we’re talking about C++ classes here. The “is a” concept translates to base classes. Look at this line in its definition to see the inheritance that makes up the “is a” concept: 

class broadcast_node : public graph_node,
public receiver, public sender {

Like much of TBB, this is a template class. The template parameter is called T, and it represents a message type that will be passed throughout the nodes. For this starter example, we won’t pass messages around. For that, TBB includes a filler message type that’s really just an empty class. It’s called continue_msg. That’s what we’ll use for our definition.

Creating the initial broadcast_node is just a matter of declaring the message type, and calling a constructor, passing in the graph, like so:

broadcast_node starter(g);

That will suffice for the starter node, which starts the ball rolling. For this example, I’ll have two nodes follow this node. For these nodes, we’ll use a type of node called a continue_node. A continue_node is similar to a broadcast node, except it includes a body of code that runs. The body of code sits in a separate class that we create. Look carefully at the declaration line of the constructor:

continue_node( graph &g, Body body )

We don’t have to formally declare the Body type when we call the constructor; the C++ compiler infers it from what we pass in. So here are a couple of lines of code that create two continue_node instances: 

continue_node A(g, body("A"));
continue_node B(g, body("B"));

This uses a class called body, that we’ll create next. But notice how I didn’t have to include the body type as a template parameter. So now let’s build a body. This is the code that will actually run when these nodes are executed:

class body {
string myname;
body(const char* _myname) : myname(_myname) { }
void operator() (continue_msg) const {
cout << myname << endl; } };

(This code is similar to an example in the documentation, which you can find here. The sample is in the second page of the Flow Graph section.) 

The body doesn't do much, other than provide an operator that prints out a string. But it also includes a copy constructor. That's because TBB copies instances of the body and doesn't use the actual one you passed in. (If you're curious about that, we can discuss it in the comments.)

Next, we need to hook together the nodes into an actual graph. Simply adding them to the graph doesn't connect them. There's a simple template function included in TBB for that:

make_edge(starter, A);
make_edge(starter, B);

Here, then, is the entire sequence of code after declaring the body type. The only part missing is the one that starts the ball rolling, which is the try_put line: 

graph g;
broadcast_node starter(g);

continue_node A(g, body("A"));
continue_node B(g, body("B"));

make_edge(starter, A);
make_edge(starter, B);


Now let's hear from you. One thing you might try is passing some messages around. We can explore that next time, but meanwhile, I'd love to hear if you've tried it yet and how it turned out.

Posted on November 8, 2012 by Jeff Cogswell, Geeknet Contributing Editor