HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
withScopedParallelism.h
Go to the documentation of this file.
1 //
2 // Copyright 2021 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_WORK_WITH_SCOPED_PARALLELISM_H
25 #define PXR_BASE_WORK_WITH_SCOPED_PARALLELISM_H
26 
27 ///\file work/withScopedParallelism.h
28 
29 #include "pxr/pxr.h"
30 #include "pxr/base/work/api.h"
32 #include "pxr/base/tf/pyLock.h"
33 
34 #include <tbb/task_arena.h>
35 
36 #include <utility>
37 
39 
40 /// Invoke \p fn, ensuring that all wait operations on concurrent constructs
41 /// invoked by the calling thread only take tasks created within the scope of \p
42 /// fn 's execution.
43 ///
44 /// Ordinarily when a thread invokes a wait operation on a concurrent construct
45 /// (e.g. the explicit WorkDispatcher::Wait(), or the implicit wait in loops
46 /// like WorkParallelForEach()) it joins the pool of worker threads and executes
47 /// tasks to help complete the work. This is good, since the calling thread
48 /// does useful work instead of busy waiting or sleeping until the work has
49 /// completed. However, this can be problematic depending on the calling
50 /// context, and which tasks the waiting thread executes.
51 ///
52 /// For example, consider the following example: a demand-populated resource
53 /// cache.
54 ///
55 /// \code
56 /// ResourceHandle
57 /// GetResource(ResourceKey key) {
58 /// // Attempt to lookup/insert an entry for \p key. If we insert the
59 /// // element, then populate the resource.
60 /// ResourceAccessorAndLock accessor;
61 /// if (_resources.FindOrCreate(key, &accessor)) {
62 /// // No previous entry, so populate the resource.
63 /// WorkDispatcher wd;
64 /// wd.Run( /* resource population task 1 */);
65 /// wd.Run( /* resource population task 2 */);
66 /// wd.Run( /* resource population task 3 */);
67 /// WorkParallelForN( /* parallel population code */);
68 /// wd.Wait();
69 /// /* Store resource data. */
70 /// }
71 /// return *accessor.first;
72 /// }
73 /// \endcode
74 ///
75 /// Here when a caller has requested the resource for \p key for the first time,
76 /// we do the work to populate the resource while holding a lock on that
77 /// resource entry in the cache. The problem is that when the calling thread
78 /// waits for work to complete, if it picks up tasks unrelated to this context
79 /// and those tasks attempt to call GetResource() with the same key, the process
80 /// will deadlock.
81 ///
82 /// This can be fixed by using WorkWithScopedParallelism() to ensure that the
83 /// calling thread's wait operations only take tasks that were created during
84 /// the scope of the population work:
85 ///
86 /// \code
87 /// ResourceHandle
88 /// GetResource(ResourceKey key) {
89 /// // Attempt to lookup/insert an entry for \p key. If we insert the
90 /// // element, then populate the resource.
91 /// ResourceAccessorAndLock accessor;
92 /// if (_resources.FindOrCreate(key, &accessor)) {
93 /// // No previous entry, so populate the resource.
94 /// WorkWithScopedParallelism([&accessor]() {
95 /// WorkDispatcher wd;
96 /// wd.Run( /* resource population task 1 */);
97 /// wd.Run( /* resource population task 2 */);
98 /// wd.Run( /* resource population task 3 */);
99 /// WorkParallelForN( /* parallel population code */);
100 /// }
101 /// /* Store resource data. */
102 /// }
103 /// return *accessor.first;
104 /// }
105 ///
106 /// This limits parallelism by only a small degree. It's only the waiting
107 /// thread that restricts the tasks it can take to the protected scope: all
108 /// other worker threads continue unhindered.
109 ///
110 /// If Python support is enabled and \p dropPythonGIL is true, this function
111 /// ensures the GIL is released before invoking \p fn. If this function
112 /// released the GIL, it reacquires it before returning.
113 ///
114 template <class Fn>
115 auto
116 WorkWithScopedParallelism(Fn &&fn, bool dropPythonGIL=true)
117 {
118  if (dropPythonGIL) {
120  return tbb::this_task_arena::isolate(std::forward<Fn>(fn));
121  }
122  else {
123  return tbb::this_task_arena::isolate(std::forward<Fn>(fn));
124  }
125 }
126 
127 /// Similar to WorkWithScopedParallelism(), but pass a WorkDispatcher instance
128 /// to \p fn for its use during the scoped parallelism. Accordingly, \p fn must
129 /// accept a WorkDispatcher lvalue reference argument. After \p fn returns but
130 /// before the scoped parallelism ends, call WorkDispatcher::Wait() on the
131 /// dispatcher instance. The \p dropPythonGIL argument has the same meaning as
132 /// it does for WorkWithScopedParallelism().
133 template <class Fn>
134 auto
135 WorkWithScopedDispatcher(Fn &&fn, bool dropPythonGIL=true)
136 {
137  return WorkWithScopedParallelism([&fn]() {
138  WorkDispatcher dispatcher;
139  return std::forward<Fn>(fn)(dispatcher);
140  // dispatcher's destructor invokes Wait() here.
141  }, dropPythonGIL);
142 }
143 
145 
146 #endif // PXR_BASE_WORK_WITH_SCOPED_PARALLELISM_H
147 
#define TF_PY_ALLOW_THREADS_IN_SCOPE()
Definition: pyLock.h:198
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1432
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:91