HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
pyLock.h
Go to the documentation of this file.
1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_BASE_TF_PY_LOCK_H
8 #define PXR_BASE_TF_PY_LOCK_H
9 
10 #include "pxr/pxr.h"
11 
12 #ifdef PXR_PYTHON_SUPPORT_ENABLED
13 
15 
16 #include "pxr/base/tf/api.h"
17 
19 
20 /// \class TfPyLock
21 ///
22 /// Convenience class for accessing the Python Global Interpreter Lock.
23 ///
24 /// The Python API is not thread-safe. Accessing the Python API from outside
25 /// the context of Python execution requires extra care when multiple threads
26 /// may be present. There are various schemes for how this should be done,
27 /// and the conventions have been changing with Python versions through 2.X.
28 ///
29 /// This class provides a convenient and centralized location for managing the
30 /// Python Global Interpreter Lock and related Python Thread State.
31 ///
32 /// The easiest way to use this class is to simply create a local variable in
33 /// any function that will access the Python API. Upon construction, this will
34 /// acquire the Python lock and establish the correct thread state for the
35 /// caller. Upon exit from the current scope, when the instance is destroyed,
36 /// the thread state will be restored and the lock will be released.
37 ///
38 /// \code
39 /// void MyFunc()
40 /// {
41 /// TfPyLock dummy;
42 /// ...(access Python API)...
43 /// }
44 /// \endcode
45 ///
46 /// If you need to temporarily release the lock during execution, (to perform
47 /// blocking I/O for example), you can call Release() explicitly, then call
48 /// Acquire() again to reclaim the lock.
49 ///
50 /// \code
51 /// void MyFunc()
52 /// {
53 /// TfPyLock pyLock;
54 /// ...(access Python API)...
55 /// pyLock.Release(); // let other threads run while we're blocked
56 /// ...(some blocking I/O or long running operation)...
57 /// pyLock.Acquire();
58 /// ...(more access to Python API)...
59 /// }
60 /// \endcode
61 ///
62 /// Note that it IS EXPLICITLY OK to recursively create instances of this
63 /// class, and thus recursively acquire the GIL and thread state. It is NOT OK
64 /// to recursively attempt to Acquire() the same instance, that will have no
65 /// effect and will generate a diagnostic warning.
66 ///
67 /// This class also provides an exception-safe way to release the GIL
68 /// temporarily for blocking calls, like Py_BEGIN/END_ALLOW_THREADS in the
69 /// Python C API.
70 ///
71 /// \code
72 /// void MyFunc()
73 /// {
74 /// TfPyLock lock;
75 /// ...(access Python API)...
76 /// lock.BeginAllowThreads(); // totally unlock the GIL temporarily.
77 /// ...(some blocking I/O or long running operation)...
78 /// lock.EndAllowThreads();
79 /// ...(more access to Python API)...
80 /// }
81 /// \endcode
82 ///
83 /// This looks similar to the above example using \a Release(), but it is
84 /// different. The Python lock is recursive, so the call to \a Release() is
85 /// not guaranteed to actually release the lock, it just releases the deepest
86 /// lock. In contrast \a BeginAllowThreads() will fully unlock the GIL so that
87 /// other threads can run temporarily regardless of how many times the lock is
88 /// recursively taken.
89 ///
90 /// The valid states and transitions for this class are as follows.
91 ///
92 /// State Valid Transitions
93 /// --------------------------------------------------------------
94 /// Released Acquire() -> Acquired
95 /// Acquired Release() -> Released, BeginAllowThreads() -> AllowsThreads
96 /// AllowsThreads EndAllowThreads() -> Acquired
97 ///
98 /// Note that upon construction the class is in the Acquired state. Upon
99 /// destruction, the class will move to the Released state.
100 ///
101 /// \warning Instances of this class should only be used as automatic (stack)
102 /// variables, or in thread local storage. DO NOT create a single instance
103 /// that could be shared across multiple threads.
104 ///
105 class TfPyLock {
106 public:
107  /// Acquires the Python GIL and swaps in callers thread state.
108  TF_API TfPyLock();
109 
110  /// Releases Python GIL and restores prior threads state.
111  TF_API ~TfPyLock();
112 
113  /// (Re)acquires GIL and thread state, if previously released.
114  TF_API void Acquire();
115 
116  /// Explicitly releases GIL and thread state.
117  TF_API void Release();
118 
119  /// Unlock the GIL temporarily to allow other threads to use python.
120  /// Typically this is used to unblock threads during operations like
121  /// blocking I/O. The lock must be acquired when called.
122  TF_API void BeginAllowThreads();
123 
124  /// End allowing other threads, reacquiring the lock state.
125  /// \a BeginAllowThreads must have been successfully called first.
126  TF_API void EndAllowThreads();
127 
128 private:
129  // Non-acquiring constructor for TfPyEnsureGILUnlockedObj's use.
130  friend struct TfPyEnsureGILUnlockedObj;
131  enum _UnlockedTag { _ConstructUnlocked };
132  explicit TfPyLock(_UnlockedTag);
133 
134  PyGILState_STATE _gilState;
135  PyThreadState *_savedState;
136  bool _acquired:1;
137  bool _allowingThreads:1;
138 };
139 
140 // Helper class for TF_PY_ALLOW_THREADS_IN_SCOPE()
141 struct TfPyEnsureGILUnlockedObj
142 {
143  // Do nothing if the current thread does not have the GIL, otherwise unlock
144  // the GIL, and relock upon destruction.
145  TF_API TfPyEnsureGILUnlockedObj();
146 private:
147  TfPyLock _lock;
148 };
149 
150 /// If the current thread of execution has the python GIL, release it,
151 /// allowing python threads to run, then upon leaving the current scope
152 /// reacquire the python GIL. Otherwise, do nothing.
153 ///
154 /// For example:
155 /// \code
156 /// {
157 /// TF_PY_ALLOW_THREADS_IN_SCOPE();
158 /// // ... long running or blocking operation ...
159 /// }
160 /// \endcode
161 ///
162 /// This is functionally similar to the following, except that it does nothing
163 /// in case the current thread of execution does not have the GIL.
164 ///
165 /// \code
166 /// {
167 /// TfPyLock lock; lock.BeginAllowThreads();
168 /// // ... long running or blocking operation ...
169 /// }
170 /// \endcode
171 ///
172 /// \hideinitializer
173 #define TF_PY_ALLOW_THREADS_IN_SCOPE() \
174  TfPyEnsureGILUnlockedObj __py_lock_allow_threads__
175 
177 
178 #else
179 
180 // When python is disabled, we stub this macro out to nothing.
181 #define TF_PY_ALLOW_THREADS_IN_SCOPE()
182 
183 #endif // PXR_PYTHON_SUPPORT_ENABLED
184 
185 #endif // PXR_BASE_TF_PY_LOCK_H
#define TF_API
Definition: api.h:23
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74