HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
withScopedParallelism.h File Reference
#include "pxr/pxr.h"
#include "pxr/base/work/api.h"
#include "pxr/base/work/dispatcher.h"
#include "pxr/base/tf/pyLock.h"
#include <tbb/task_arena.h>
#include <utility>
+ Include dependency graph for withScopedParallelism.h:

Go to the source code of this file.

Functions

template<class Fn >
PXR_NAMESPACE_OPEN_SCOPE auto WorkWithScopedParallelism (Fn &&fn, bool dropPythonGIL=true)
 
template<class Fn >
auto WorkWithScopedDispatcher (Fn &&fn, bool dropPythonGIL=true)
 

Function Documentation

template<class Fn >
auto WorkWithScopedDispatcher ( Fn &&  fn,
bool  dropPythonGIL = true 
)

Similar to WorkWithScopedParallelism(), but pass a WorkDispatcher instance to fn for its use during the scoped parallelism. Accordingly, fn must accept a WorkDispatcher lvalue reference argument. After fn returns but before the scoped parallelism ends, call WorkDispatcher::Wait() on the dispatcher instance. The dropPythonGIL argument has the same meaning as it does for WorkWithScopedParallelism().

Definition at line 119 of file withScopedParallelism.h.

template<class Fn >
PXR_NAMESPACE_OPEN_SCOPE auto WorkWithScopedParallelism ( Fn &&  fn,
bool  dropPythonGIL = true 
)

Invoke fn, ensuring that all wait operations on concurrent constructs invoked by the calling thread only take tasks created within the scope of fn 's execution.

Ordinarily when a thread invokes a wait operation on a concurrent construct (e.g. the explicit WorkDispatcher::Wait(), or the implicit wait in loops like WorkParallelForEach()) it joins the pool of worker threads and executes tasks to help complete the work. This is good, since the calling thread does useful work instead of busy waiting or sleeping until the work has completed. However, this can be problematic depending on the calling context, and which tasks the waiting thread executes.

For example, consider the following example: a demand-populated resource cache.

ResourceHandle
GetResource(ResourceKey key) {
// Attempt to lookup/insert an entry for \p key. If we insert the
// element, then populate the resource.
ResourceAccessorAndLock accessor;
if (_resources.FindOrCreate(key, &accessor)) {
// No previous entry, so populate the resource.
wd.Run( /* resource population task 1 */);
wd.Run( /* resource population task 2 */);
wd.Run( /* resource population task 3 */);
WorkParallelForN( /* parallel population code */);
wd.Wait();
/* Store resource data. */
}
return *accessor.first;
}

Here when a caller has requested the resource for key for the first time, we do the work to populate the resource while holding a lock on that resource entry in the cache. The problem is that when the calling thread waits for work to complete, if it picks up tasks unrelated to this context and those tasks attempt to call GetResource() with the same key, the process will deadlock.

This can be fixed by using WorkWithScopedParallelism() to ensure that the calling thread's wait operations only take tasks that were created during the scope of the population work:

ResourceHandle
GetResource(ResourceKey key) {
// Attempt to lookup/insert an entry for \p key. If we insert the
// element, then populate the resource.
ResourceAccessorAndLock accessor;
if (_resources.FindOrCreate(key, &accessor)) {
// No previous entry, so populate the resource.
WorkWithScopedParallelism([&accessor]() {
wd.Run( /* resource population task 1 */);
wd.Run( /* resource population task 2 */);
wd.Run( /* resource population task 3 */);
WorkParallelForN( /* parallel population code */);
}
/* Store resource data. */
}
return *accessor.first;
}

This limits parallelism by only a small degree. It's only the waiting thread that restricts the tasks it can take to the protected scope: all other worker threads continue unhindered.

If Python support is enabled and dropPythonGIL is true, this function ensures the GIL is released before invoking fn. If this function released the GIL, it reacquires it before returning.

Definition at line 100 of file withScopedParallelism.h.