Search code examples
c++c++11parallel-processingtbb

Cannot Return Values When Passing Function by Reference To TBB Task


I'm getting my feet wet with Intel TBB and am trying to figure out why I cannot populate a vector passed in by reference to a TBB Task when I also pass in a function by reference.

Here is the code:

// tbbTesting.cpp : Defines the entry point for the console application.

#include "stdafx.h"

#include "tbb/task.h"

#include <functional>
#include <iostream>
#include <random>

#define NUM_POINTS 10


void myFunc(std::vector<double>& numbers)
{
   std::mt19937_64 gen;
   std::uniform_real_distribution<double> dis(0.0, 1000.0);

   for (size_t i = 0; i < NUM_POINTS; i++)
   {
      auto val = dis(gen);
      std::cout << val << std::endl; //proper values generated
      numbers.push_back(val);  //why is this failing?
   }

   std::cout << std::endl;

   for (auto i : numbers)
   {
      std::cout << numbers[i] << std::endl; //garbage values
   }
}


class TASK_generateRandomNumbers : public tbb::task
{
public:
   TASK_generateRandomNumbers(std::function<void(std::vector<double>&)>& fnc, 
std::vector<double>& nums) : _fnc(fnc), _numbers(nums) {}
   ~TASK_generateRandomNumbers() {};

   tbb::task* execute()
   {
      _fnc(_numbers);
      return nullptr;
   }

private:
   std::function<void(std::vector<double>&)>& _fnc;
   std::vector<double>& _numbers;
};


class Manager
{
public:
   Manager() { _numbers.reserve(NUM_POINTS); }
   ~Manager() {}

   void GenerateNumbers()
   {
        _fnc = std::bind(&myFunc, _numbers);
        TASK_generateRandomNumbers* t = new(tbb::task::allocate_root()) 
        TASK_generateRandomNumbers(_fnc, _numbers);
        tbb::task::spawn_root_and_wait(*t);
   }

   auto GetNumbers() const { return _numbers; }

private:
   std::function<void(std::vector<double>&)> _fnc;
   std::vector<double> _numbers;
};



int main()
{
   Manager mgr;
   mgr.GenerateNumbers();
   auto numbers = mgr.GetNumbers(); //returns empty
}

When the execute method performs the operation, I can get values when passing the vector by reference.

When the execute method has to call a function, I get garbage data printed to the console (push_back failing?) and I get an empty container on return.

Can anyone see what I'm missing? Thanks.


Solution

  • I have found a couple of bugs that have nothing to do with tbb.

    1) Your myFunc is using range for incorrectly. It does not return an index but each value directly in the vector in turn. Your code is casting each double to an int and using that as index into the array which is why you are gettign garbage.

    2) When you use std::bind to create a functor the arguments are copied by value. If you want to pass in a reference then you need to use std::ref to wrap the argument.

    If you are using c++11 then you might want to consider using a lambda rather than bind.

    I've written a small program using your myFunc in different ways: with and without using std::ref and also a lambda example. You should see that it generates the same numbers 3 times but when it tries to print out v1 it wont contain anything because the generated values were placed in a copy.

    #include <vector>
    #include <random>
    #include <iostream>
    #include <functional>
    
    constexpr size_t NUM_POINTS = 10;
    
    void myFunc(std::vector<double>& numbers)
    {
        std::mt19937_64 gen;
        std::uniform_real_distribution<double> dis(0.0, 1000.0);
    
        for (size_t i = 0; i < NUM_POINTS; i++)
        {
             auto val = dis(gen);
             std::cout << val << std::endl; //proper values generated
             numbers.push_back(val);  //why is this failing? it's not
        }
    
        std::cout << std::endl;
    }
    
    void printNumbers(std::vector<double>const& numbers)
    {
        for (auto number : numbers)
        {
            std::cout << number << std::endl; 
        }
        std::cout << std::endl;
    }
    
    int main()
    {
        std::cout << "generating v1" << std::endl;
        std::vector<double> v1;
        auto f1 = std::bind(&myFunc, v1);
        f1();
        printNumbers(v1);
    
        std::cout << "generating v2" << std::endl;
        std::vector<double> v2;
        auto f2= std::bind(&myFunc, std::ref(v2));
        f2();
        printNumbers(v2);
    
        std::cout << "generating v3" << std::endl;
        std::vector<double> v3;
        auto f3 = [&v3]() { myFunc(v3); };  //using a lambda
        f3();
        printNumbers(v3);
    
        return 0;
    }