Most implementations I've seen define the order of execution of tasks implicitly by their dependencies. Something like:
Task taskA, taskB, taskC, taskD, taskE;
taskC.DependsOn(taskA, taskB);
taskD.DependsOn(taskC);
taskE.DependsOn(taskA, taskC);
pThreadPool->Run(); // Block until all tasks complete
Now figuring out which task goes first from just the dependencies is quite trivial in the above example, but it gets considerably more difficult in a real world situation:
![]() |
| Frostbite 2 task graph |
While nothing stops ARF applications from using a task-execution framework like the one above (after all ARF is just a bunch of related libraries and is not really an "engine"), by and large I still prefer explicitly ordering tasks in code simply because it's much clearer:
pThreadPool->AddTask(taskA);
pThreadPool->AddTask(taskB);
pThreadPool->Run(); // Block until all tasks complete
pThreadPool->AddTask(taskC);
pThreadPool->Run();
pThreadPool->Run();
pThreadPool->AddTask(taskD);
pThreadPool->AddTask(taskE);
pThreadPool->Run();
This is functionally equivalent to the one above but isn't the ordering immediately apparent?
pThreadPool->AddTask(taskE);
pThreadPool->Run();
This is functionally equivalent to the one above but isn't the ordering immediately apparent?
In any case, in addition to the usual synchronization primitives and atomic instructions, one of the ARF modules also provides a robust and lightweight thread-pool implementation that supports multiple task consumers and producers, with the total amount of code within a contention block only about 8 instructions long, so squeezing maximum performance out of multi-threaded applications becomes less of a chore.

No comments:
Post a Comment