Task Groups

As it is difficult to express all segments of a program in the task parallel model, PFunc allows users to mix task parallelism with SPMD-style programming through the use of task groups. Tasks within the same group can communicate using built-in point-to-point and collective operations. Each group object has three pieces of information associated with it. These are:

  • Id uniquely identifies each group and is used for debugging purposes.
  • Size of the group. Each group can have atmost size tasks.
  • Barrier type to be executed. PFunc provides three types of barriers.
    • Spinning barriers are the default barriers that are executed. It is the ideal barrier type when the wait time is expected to be small.
    • Waiting barriers on the other hand can be used when the wait times are expected to be large.
    • Stealing barriers enable a thread that is executing a task that is waiting on a barrier to select and execute tasks from other groups. Note that tasks from the same group cannot be picked up for execution as this might result in deadlocks.

In addition, each task belonging to a group is given an unique rank in that group that can be used for point-to-point communications. The functions that are available to operate on groups are summarized below:

Function Explanation Value
group_id_set Set the group's Id Type: unsigned int
group_id_get Get the group's Id Type: unsigned int
group_size_set Set the group's size Type: unsigned int
group_size_get Get the group's size Type: unsigned int
group_barrier_set Set the group's barrier type BARRIER_SPIN (default), BARRIER_STEAL, BARRIER_WAIT
group_barrier_get Get the group's barrier type BARRIER_SPIN (default), BARRIER_STEAL, BARRIER_WAIT
group_rank Get my rank in my group 0 to num_tasks
group_size Get my size in my group Type: unsigned int
pfunc_<schedpolicy>_group_init Initialize the C group None
pfunc_<schedpolicy>_group_clear Clear the C group None

Groups in C ==Groups are accessed through objects of type pfunc_<schedpolicy>_group_t, where <schedpolicy> is one of cilk, lifo, fifo or prio. Consider the following example that demonstrates simple use of the groups:

/* addrof() function returns the address of the passed parameter -- needed because ampersand is giving an error in wiki */

void parallel_foo (void* arg) {
  unsigned int rank, size, id;

  pfunc_cilk_group_size( addrof(size));

  /* Print the rank and size */
  print ("Here: ", rank, size);
int main () {
  pfunc_cilk_task_t tasks[10];
  pfunc_cilk_group_t group;
  unsigned int num_queues = 4;
  const unsigned int num_threads_per_queue[] = {1,1,1,1};
  pfunc_cilk_taskmgr_t cilk_tmanager;
  int i;

  pfunc_cilk_taskmgr_init (addrof(cilk_tmanager), num_queues, num_threads_per_queue, NULL);
  pfunc_cilk_group_init (addrof(group));
  pfunc_cilk_group_size_set (group, 10);

  for (i=0; i<10; ++i) {
    pfunc_cilk_task_init (addrof(tasks[i]));
    pfunc_cilk_run_c (cilk_taskmgr, tasks[i], NULL, group, parallel_foo, NULL);

  pfunc_cilk_wait_all (cilk_tmanager, tasks, 10);

  pfunc_cilk_group_clear (addrof(group));
  pfunc_cilk_taskmgr_clear (addrof(cilk_tmanager));

  return 0;

In the above code sample, each spawned task prints its rank in the group before and the size of the group before exiting. The rank and size are obtained by a calls to pfunc_cilk_group_rank and pfunc_cilk_group_size. Notice that these functions do not take the group structure as a parameter. PFunc implicitly uses the group that the task was spawned with as an additional argument to these function calls. As usual, since we are operating in C, group objects have to be initialized and cleared before and after use.

Groups in C++

In C++, task groups are implemented through of type group that can be accessed as a nested type of the generated library instance description. Other than this, the behavior is similar to that of the groups in C. The following code sample gives the C++ equivalent of the C example shown before:

struct parallel_foo {
  void operator()() {
    unsigned int rank, size, id;


    /* Print the rank and size */
    std::cout << "Here: " << rank << " of " << size << std::endl;

/* Library instance description */
typedef pfunc::generator<cilkS, /* scheduling policy */
                         pfunc::use_default, /* compare */
                         parallel_foo> my_pfunc; /*function object*/

int main () {
  my_pfunc::task tasks[10];
  my_pfunc::group group;
  parallel_foo work[10];
  unsigned int num_queues = 4;
  const unsigned int num_threads_per_queue[] = {1,1,1,1};

  /* Initialize a global instance of the library */
  my_pfunc::taskmgr cilk_tmanager (num_queues, num_threads_per_queue);
  pfunc::init (cilk_tmanager);

  /* Set the size of the group */
  pfunc::group_size_set (group, 10);

  /* Spawn the tasks */
  for (int i=0; i<10; ++i) {
    work[i].initialize (i);
    pfunc::spawn (tasks[i], group, work[i]);

  /* Wait for the tasks and clear the task handle */
  pfunc::wait_all (tasks, 10);

  /* Clear the library */
  pfunc::clear ();
  return 0;
Last modified 12 years ago Last modified on Nov 1, 2009 1:58:44 PM