On-going task streams.

On-going task streams.

We have a couple of applications which all share the same basic task model, and we are struggling to get TBB to behave as desired.  In this model we want to dispatch tasks on an on-going basis, potentially “forever” (i.e. until the machine or software fails) but with the capability to reach the end.  In other words we have a stream of tasks, and they are all fire and forget... except for the need to wait for them all to finish at the end.  Sometimes tasks will want to spawn more tasks as part of the same stream.  We have tried many things using TBB, but haven’t found an approach that is simple and natural.

 

The ideal model would be to create a task_group and call its run method for each task.  When we reach the end the main thread (which is handling I/O & event loop) it simply calls wait() on the group.  All the tasks finish and the thread can terminate the application.  This approach works... for a while.  What appears to be happening, however, is that tasks finish running but are never deallocated and removed from the task_group.  Eventually memory problems result.

Our current solution is a hack — we create a circular buffer of task_groups and a thread that waits for the oldest, destroys it, replaces it, and then advances an index that task spawners are using to choose the task_group to spawn into.  This is a pretty horrid and fragile solution, and it would preferable to just have one task_group that we spawn into and which automatically deallocates tasks which complete.

Have we simply missed a feature in TBB?  Is there a simple and elegant way to accomplish this?  It seems very surprising that this isn’t more natural in TBB because this is the kind of basic task model I have always used in all my applications — far more prevalent than parallel for loops, and all the other fancy features TBB supports!

 

 

4 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Hi Andrew,

We have looked through the code running the example you described under debugger and everything seems correct. Tasks are deallocated and removed. Could you please provide small reproducer so that we can find the issue?

Regards, Aleksei

 

We will attempt to create a reproducer when we can put some time against it, but for the moment you've partially answered my question:  you believe there is no reason that a task_group should create problems if it is used for a very long time.  Could it be something related to how we are spawning tasks (i.e. via C++ lambdas)?  We've gone through numerous variations, however the current code is doing one of these two things:

            auto doTask = [=] { this->DoTask(); };

            // simple case
            _taskGroup->run(doTask);

            // spawn multiple case
            _taskGroup->run([=]
            {
                size_t c = count;
                while (c--)
                    _taskGroup->run(doTask);
            });

The DoTask function can do all manner of things and runs for a highly variable amount of time.  It is, of course, possible that the memory accumulation we're seeing is in our tasks, however switching to a scheme where we rotate through creating/destroying task_groups fixes the issue.

 

Interestingly, have you tried not to create/destroy task_groups but rather call task_group::wait() from time to time using the same task_group instance? Would that result in similar behavior?

Regards, Aleksei

 

Leave a Comment

Please sign in to add a comment. Not a member? Join today