|
| 1 | +<ins> |
| 2 | +<cxx-clause id="parallel.task_block"> |
| 3 | + <h1>Task Block</h1> |
| 4 | + |
| 5 | + <cxx-section id="parallel.task_block.synopsis"> |
| 6 | + <h1>Header <code><experimental/task_block></code> synopsis</h1> |
| 7 | + |
| 8 | + <pre> |
| 9 | +namespace std { |
| 10 | +namespace experimental { |
| 11 | +namespace parallel { |
| 12 | +inline namespace v2 { |
| 13 | + class task_cancelled_exception; |
| 14 | + |
| 15 | + class task_block; |
| 16 | + |
| 17 | + template<class F> |
| 18 | + void define_task_block(F&& f); |
| 19 | + |
| 20 | + template<class f> |
| 21 | + void define_task_block_restore_thread(F&& f); |
| 22 | +} |
| 23 | +} |
| 24 | +} |
| 25 | +} |
| 26 | + </pre> |
| 27 | + </cxx-section> |
| 28 | + |
| 29 | + <cxx-section id="parallel.task_block.task_cancelled_exception"> |
| 30 | + <h1>Class <code>task_cancelled_exception</code></h1> |
| 31 | + <pre> |
| 32 | + |
| 33 | +namespace std { |
| 34 | +namespace experimental { |
| 35 | +namespace parallel |
| 36 | +inline namespace v2 { |
| 37 | + |
| 38 | + class task_cancelled_exception : public exception |
| 39 | + { |
| 40 | + public: |
| 41 | + task_cancelled_exception() noexcept; |
| 42 | + virtual const char* what() const noexcept; |
| 43 | + }; |
| 44 | +} |
| 45 | +} |
| 46 | +} |
| 47 | +} |
| 48 | + </pre> |
| 49 | + |
| 50 | + <p> |
| 51 | + The class <code>task_cancelled_exception</code> defines the type of objects thrown by |
| 52 | + <code>task_block::run</code> or <code>task_block::wait</code> if they detect than an |
| 53 | + exception is pending within the current parallel block. See <cxx-ref to="parallel.task_block.exceptions"></cxx-ref>, below. |
| 54 | + </p> |
| 55 | + |
| 56 | + <cxx-section id="parallel.task_block.task_cancelled_exception.what"> |
| 57 | + <h1><code>task_cancelled_exception</code> member function <code>what</code></h1> |
| 58 | + |
| 59 | + <cxx-function> |
| 60 | + <cxx-signature>virtual const char* what() const noexcept</cxx-signature> |
| 61 | + |
| 62 | + <cxx-returns> |
| 63 | + An implementation-defined NTBS. |
| 64 | + </cxx-returns> |
| 65 | + </cxx-function> |
| 66 | + </cxx-section> |
| 67 | + </cxx-section> |
| 68 | + |
| 69 | + <cxx-section id="parallel.task_block.class"> |
| 70 | + <h1>Class <code>task_block</code></h1> |
| 71 | + <pre> |
| 72 | + |
| 73 | +namespace std { |
| 74 | +namespace experimental { |
| 75 | +namespace parallel { |
| 76 | +inline namespace v2 { |
| 77 | + |
| 78 | + class task_block |
| 79 | + { |
| 80 | + private: |
| 81 | + ~task_block(); |
| 82 | + |
| 83 | + public: |
| 84 | + task_block(const task_block&) = delete; |
| 85 | + task_block& operator=(const task_block&) = delete; |
| 86 | + void operator&() const = delete; |
| 87 | + |
| 88 | + template<class F> |
| 89 | + void run(F&& f); |
| 90 | + |
| 91 | + void wait(); |
| 92 | + }; |
| 93 | +} |
| 94 | +} |
| 95 | +} |
| 96 | +} |
| 97 | + </pre> |
| 98 | + |
| 99 | + <p> |
| 100 | + The class <code>task_block</code> defines an interface for forking and joining parallel tasks. The <code>define_task_block</code> and <code>define_task_block_restore_thread</code> function templates create an object of type <code>task_block</code> and pass a reference to that object to a user-provided function object. |
| 101 | + </p> |
| 102 | + |
| 103 | + <p> |
| 104 | + An object of class <code>task_block</code> cannot be constructed, destroyed, copied, or moved except by the implementation of the task block library. Taking the address of a <code>task_block</code> object via <code>operator&</code> is ill-formed. Obtaining its address by any other means (including <code>addressof</code>) results in a pointer with an unspecified value; dereferencing such a pointer results in undefined behavior. |
| 105 | + </p> |
| 106 | + |
| 107 | + <p> |
| 108 | + A <code>task_block</code> is <em>active</em> if it was created by the nearest enclosing task block, where “task block” refers to an |
| 109 | + invocation of <code>define_task_block</code> or <code>define_task_block_restore_thread</code> and “nearest enclosing” means the most |
| 110 | + recent invocation that has not yet completed. Code designated for execution in another thread by means other |
| 111 | + than the facilities in this section (e.g., using <code>thread</code> or <code>async</code>) are not enclosed in the task block and a |
| 112 | + <code>task_block</code> passed to (or captured by) such code is not active within that code. Performing any operation on a |
| 113 | + <code>task_block</code> that is not active results in undefined behavior. |
| 114 | + </p> |
| 115 | + |
| 116 | + <p> |
| 117 | + When the argument to <code>task_block::run</code> is called, no <code>task_block</code> is active, not even the <code>task_block</code> on which <code>run</code> was called. |
| 118 | + (The function object should not, therefore, capture a <code>task_block</code> from the surrounding block.) |
| 119 | + </p> |
| 120 | + |
| 121 | + <cxx-example> |
| 122 | + <pre>define_task_block([&](auto& tb) { |
| 123 | + tb.run([&]{ |
| 124 | + tb.run([] { f(); }); // Error: tb is not active within run |
| 125 | + define_task_block([&](auto& tb2) { // Define new task block |
| 126 | + tb2.run(f); |
| 127 | + ... |
| 128 | + }); |
| 129 | + }); |
| 130 | + ... |
| 131 | +}); |
| 132 | + </pre> |
| 133 | + </cxx-example><pre> |
| 134 | +</pre> |
| 135 | + |
| 136 | + <cxx-note> |
| 137 | + Implementations are encouraged to diagnose the above error at translation time. |
| 138 | + </cxx-note> |
| 139 | + |
| 140 | + <cxx-section id="parallel.task_block.class.run"> |
| 141 | + |
| 142 | + <h1><code>task_block</code> member function template <code>run</code></h1> |
| 143 | + |
| 144 | + <cxx-function> |
| 145 | + <cxx-signature>template<class F> void run(F&& f);</cxx-signature> |
| 146 | + |
| 147 | + <cxx-requires> |
| 148 | + <code>F</code> shall be <code>MoveConstructible</code>. <code><em>DECAY_COPY</em>(std::forward<F>(f))()</code> shall be a valid expression. |
| 149 | + </cxx-requires> |
| 150 | + |
| 151 | + <cxx-preconditions> |
| 152 | + <code>*this</code> shall be the active <code>task_block</code>. |
| 153 | + </cxx-preconditions> |
| 154 | + |
| 155 | + <cxx-effects> |
| 156 | + Evaluates <code><em>DECAY_COPY</em>(std::forward<F>(f))()</code>, where <code><em>DECAY_COPY</em>(std::forward<F>(f))</code> |
| 157 | + is evaluated synchronously within the current thread. The call to the resulting copy of the function object is |
| 158 | + permitted to run on an unspecified thread created by the implementation in an unordered fashion relative to |
| 159 | + the sequence of operations following the call to <code>run(f)</code> (the continuation), or indeterminately sequenced |
| 160 | + within the same thread as the continuation. The call to <code>run</code> synchronizes with the call to the function |
| 161 | + object. The completion of the call to the function object synchronizes with the next invocation of <code>wait</code> on |
| 162 | + the same <code>task_block</code> or completion of the nearest enclosing task block (i.e., the <code>define_task_block</code> or |
| 163 | + <code>define_task_block_restore_thread</code> that created this <code>task_block</code>). |
| 164 | + </cxx-effects> |
| 165 | + |
| 166 | + <cxx-throws> |
| 167 | + <code>task_cancelled_exception</code>, as described in <cxx-ref to="parallel.task_block.exceptions"></cxx-ref>. |
| 168 | + </cxx-throws> |
| 169 | + |
| 170 | + <cxx-remarks> |
| 171 | + The <code>run</code> function may return on a thread other than the one on which it was called; in such cases, |
| 172 | + completion of the call to <code>run</code> synchronizes with the continuation. |
| 173 | + |
| 174 | + <cxx-note> The return from <code>run</code> is ordered similarly to an ordinary function call in a single thread.</cxx-note> |
| 175 | + </cxx-remarks> |
| 176 | + |
| 177 | + <cxx-remarks> |
| 178 | + The invocation of the user-supplied function object <code>f</code> may be immediate or may be delayed until |
| 179 | + compute resources are available. <code>run</code> might or might not return before the invocation of <code>f</code> completes. |
| 180 | + </cxx-remarks> |
| 181 | + |
| 182 | + </cxx-function> |
| 183 | + </cxx-section> |
| 184 | + |
| 185 | + <cxx-section id="parallel.task_block.class.wait"> |
| 186 | + |
| 187 | + <h1><code>task_block</code> member function <code>wait</code></h1> |
| 188 | + |
| 189 | + <cxx-function> |
| 190 | + <cxx-signature>void wait();</cxx-signature> |
| 191 | + |
| 192 | + <cxx-preconditions><code>*this</code> shall be the active <code>task_block</code>.</cxx-preconditions> |
| 193 | + |
| 194 | + <cxx-effects> |
| 195 | + Blocks until the tasks spawned using this <code>task_block</code> have completed. |
| 196 | + </cxx-effects> |
| 197 | + |
| 198 | + <cxx-throws> |
| 199 | + <code>task_cancelled_exception</code>, as described in <cxx-ref to="parallel.task_block.exceptions"></cxx-ref>. |
| 200 | + </cxx-throws> |
| 201 | + |
| 202 | + <cxx-postconditions> |
| 203 | + All tasks spawned by the nearest enclosing task block have completed. |
| 204 | + </cxx-postconditions> |
| 205 | + |
| 206 | + <cxx-remarks> |
| 207 | + The <code>wait</code> function may return on a thread other than the one on which it was called; in such cases, completion of the call to <code>wait</code> synchronizes with subsequent operations. |
| 208 | + |
| 209 | + <cxx-note>The return from wait is ordered similarly to an ordinary function call in a single thread.</cxx-note> |
| 210 | + |
| 211 | + <cxx-example><pre> |
| 212 | +define_task_block([&](auto& tb) { |
| 213 | + tb.run([&]{ process(a, w, x); }); // Process a[w] through a[x] |
| 214 | + if (y < x) tb.wait(); // Wait if overlap between [w,x) and [y,z) |
| 215 | + process(a, y, z); // Process a[y] through a[z] |
| 216 | +}); |
| 217 | +</pre> |
| 218 | + </cxx-example> |
| 219 | + </cxx-remarks> |
| 220 | + </cxx-function> |
| 221 | + </cxx-section> |
| 222 | + </cxx-section> |
| 223 | + |
| 224 | + <cxx-section id="parallel.task_block.define_task_block"> |
| 225 | + <h1>Function template <code>define_task_block</code></h1> |
| 226 | + |
| 227 | + <cxx-function> |
| 228 | + <cxx-signature>template<class F> |
| 229 | +void define_task_block(F&& f); |
| 230 | + </cxx-signature> |
| 231 | + |
| 232 | + <cxx-signature>template<class F> |
| 233 | +void define_task_block_restore_thread(F&& f); |
| 234 | + </cxx-signature> |
| 235 | + |
| 236 | + <cxx-requires> |
| 237 | + Given an lvalue <code>tb</code> of type <code>task_block</code>, the expression <code>f(tb)</code> shall be well-formed |
| 238 | + </cxx-requires> |
| 239 | + |
| 240 | + <cxx-effects> |
| 241 | + Constructs a <code>task_block</code> <code>tb</code> and calls <code>f(tb)</code>. |
| 242 | + </cxx-effects> |
| 243 | + |
| 244 | + <cxx-throws> |
| 245 | + <code>exception_list</code>, as specified in <cxx-ref to="parallel.task_block.exceptions"></cxx-ref>. |
| 246 | + </cxx-throws> |
| 247 | + |
| 248 | + <cxx-postconditions> |
| 249 | + All tasks spawned from <code>f</code> have finished execution. |
| 250 | + </cxx-postconditions> |
| 251 | + |
| 252 | + <cxx-remarks> |
| 253 | + The <code>define_task_block</code> function may return on a thread other than the one on which it was called |
| 254 | + unless there are no task blocks active on entry to <code>define_task_block</code> (see <cxx-ref to="parallel.task_block.class"></cxx-ref>), in which |
| 255 | + case the function returns on the original thread. When <code>define_task_block</code> returns on a different thread, |
| 256 | + it synchronizes with operations following the call. <cxx-note> The return from define_task_block is ordered |
| 257 | + similarly to an ordinary function call in a single thread.</cxx-note> The <code>define_task_block_restore_thread</code> |
| 258 | + function always returns on the same thread as the one on which it was called. |
| 259 | + </cxx-remarks> |
| 260 | + |
| 261 | + <cxx-notes> |
| 262 | + It is expected (but not mandated) that <code>f</code> will (directly or indirectly) call <code>tb.run(<em>function-object</em>)</code>. |
| 263 | + </cxx-notes> |
| 264 | + </cxx-function> |
| 265 | + </cxx-section> |
| 266 | + |
| 267 | + <cxx-section id="parallel.task_block.exceptions"> |
| 268 | + <h1>Exception Handling</h1> |
| 269 | + |
| 270 | + <p> |
| 271 | + Every <code>task_block</code> has an associated exception list. When the task block starts, its associated exception list is empty. |
| 272 | + </p> |
| 273 | + |
| 274 | + <p> |
| 275 | + When an exception is thrown from the user-provided function object passed to <code>define_task_block</code> or |
| 276 | + <code>define_task_block_restore_thread</code>, it is added to the exception list for that task block. Similarly, when |
| 277 | + an exception is thrown from the user-provided function object passed into <code>task_block::run</code>, the exception |
| 278 | + object is added to the exception list associated with the nearest enclosing task block. In both cases, an |
| 279 | + implementation may discard any pending tasks that have not yet been invoked. Tasks that are already in |
| 280 | + progress are not interrupted except at a call to <code>task_block::run</code> or <code>task_block::wait</code> as described below. |
| 281 | + </p> |
| 282 | + |
| 283 | + <p> |
| 284 | + If the implementation is able to detect that an exception has been thrown by another task within |
| 285 | + the same nearest enclosing task block, then <code>task_block::run</code> or <code>task_block::wait</code> may throw |
| 286 | + <code>task_canceled_exception</code>; these instances of <code>task_canceled_exception</code> are not added to the exception |
| 287 | + list of the corresponding task block. |
| 288 | + </p> |
| 289 | + |
| 290 | + <p> |
| 291 | + When a task block finishes with a non-empty exception list, the exceptions are aggregated into an <code>exception_list</code> object, which is then thrown from the task block. |
| 292 | + </p> |
| 293 | + |
| 294 | + <p> |
| 295 | + The order of the exceptions in the <code>exception_list</code> object is unspecified. |
| 296 | + </p> |
| 297 | + </cxx-section> |
| 298 | +</cxx-clause> |
| 299 | +</ins> |
| 300 | + |
0 commit comments