Reference documentation for deal.II version GIT relicensing-1062-gc06da148b8 2024-07-15 19:20:02+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
task_result.h
Go to the documentation of this file.
1// ---------------------------------------------------------------------
2//
3// Copyright (C) 2023 - 2024 by the deal.II authors
4//
5// This file is part of the deal.II library.
6//
7// The deal.II library is free software; you can use it, redistribute
8// it, and/or modify it under the terms of the GNU Lesser General
9// Public License as published by the Free Software Foundation; either
10// version 2.1 of the License, or (at your option) any later version.
11// The full text of the license can be found in the file LICENSE.md at
12// the top level directory of deal.II.
13//
14// ---------------------------------------------------------------------
15
16#ifndef dealii_task_result_h
17#define dealii_task_result_h
18
19
20#include <deal.II/base/config.h>
21
24
25#include <atomic>
26#include <mutex>
27#include <optional>
28
30
36namespace Threads
37{
86 template <typename T>
88 {
89 public:
96 : result_is_available(false)
97 {}
98
104 : result_is_available(false)
105 , task(task)
106 {
107 // It is conceivable that the task has already finished if we
108 // come to this point, but that is not important to us here:
109 // we will simply find out once someone calls get()
110 }
111
117 TaskResult(const TaskResult<T> &) = delete;
118
124 TaskResult(TaskResult<T> &&other) noexcept;
125
183 ~TaskResult();
184
242 void
243 operator=(const Task<T> &t);
244
253 void
254 clear();
255
260 void
261 join();
262
269 const T &
270 value() const;
271
272 private:
277 mutable std::atomic<bool> result_is_available;
278
286 mutable std::optional<Task<T>> task;
287
291 mutable std::optional<T> task_result;
292
296 mutable std::mutex mutex;
297
303 void
304 wait_and_move_result() const;
305 };
306
307
308 // ------------------------------- inline functions --------------------------
309
310
311 template <typename T>
313 {
314 // First lock the other object, then move the members of the other
315 // object and reset it. Note that we do not have to wait for
316 // the other object's task to finish (nor should we).
317 std::lock_guard<std::mutex> lock(other.mutex);
318
319 result_is_available = other.result_is_available.load();
320 other.result_is_available = false;
321
322 task = std::move(other.task);
323 other.task.reset();
324
325 task_result = std::move(other.task_result);
326 other.task_result.reset();
327 }
328
329
330
331 template <typename T>
333 {
334 // Ensure that there is no currently running task. As
335 // documented, we consider this an error. Since clear()
336 // also checks for this error, we can just defer to that function:
337 clear();
338 }
339
340
341
342 template <typename T>
343 inline void
345 {
346 // First ensure that there is no currently running task. As
347 // documented, we consider this an error. Since clear()
348 // also checks for this error, we can just defer to that function:
349 clear();
350
351 // Having established that there is no previous task still running,
352 // set the current task as the one we're waiting for:
353 {
354 std::lock_guard<std::mutex> lock(mutex);
355 task = t;
356 }
357 }
358
359
360
361 template <typename T>
362 inline void
364 {
365 // If we have waited before, then return immediately:
366 if (result_is_available)
367 return;
368 else
369 // If we have not waited, wait now. We need to use the double-checking
370 // pattern to ensure that if two threads get to this place at the same
371 // time, one returns right away while the other does the work. Note
372 // that this happens under the lock, so only one thread gets to be in
373 // this code block at the same time:
374 {
375 std::lock_guard<std::mutex> lock(mutex);
376
377 if (result_is_available)
378 return;
379 else
380 Assert(task.has_value() == false,
381 ExcMessage("You cannot destroy a TaskResult object "
382 "while it is still waiting for its associated task "
383 "to finish. See the documentation of this class' "
384 "destructor for more information."));
385 }
386 std::lock_guard<std::mutex> lock(mutex);
387 // First make clear that the result is no longer available:
388 result_is_available = false;
389 // Then abandon a previous task, should there have been one. Also abandon
390 // any previously available returned object
391 task.reset();
392 task_result.reset();
393 }
394
395
396
397 template <typename T>
398 inline void
400 {
401 // If we have waited before, then return immediately:
402 if (result_is_available)
403 return;
404 else // If we have not waited, wait now. We need to use the double-checking
405 // pattern to ensure that if two threads get to this place at the same
406 // time, one returns right away while the other does the work. Note
407 // that this happens under the lock, so only one thread gets to be in
408 // this code block at the same time:
409 {
410 std::lock_guard<std::mutex> lock(mutex);
411 if (result_is_available)
412 return;
413 else
414 // If there is a task, wait for it to finish. We could then move
415 // the result, but it's fine to postpone that until someone actually
416 // asks for the result.
417 if (task.has_value())
418 task.value().join();
419 }
420 }
421
422
423
424 template <typename T>
425 inline const T &
427 {
428 if (!result_is_available)
429 wait_and_move_result();
430 return task_result.value();
431 }
432
433
434
435 template <typename T>
436 inline void
438 {
439 // If we have waited before, then return immediately:
440 if (result_is_available)
441 return;
442 else
443 // If we have not waited, wait now. We need to use the double-checking
444 // pattern to ensure that if two threads get to this place at the same
445 // time, one returns right away while the other does the work. Note
446 // that this happens under the lock, so only one thread gets to be in
447 // this code block at the same time:
448 {
449 std::lock_guard<std::mutex> lock(mutex);
450
451 if (result_is_available)
452 return;
453 else
454 {
455 Assert(task.has_value(),
456 ExcMessage("You cannot wait for the result of a TaskResult "
457 "object that has no task associated with it."));
458 task.value().join();
459 task_result = std::move(task.value().return_value());
460 task.reset();
461
462 result_is_available = true;
463 }
464 }
465 }
466
467} // namespace Threads
468
476#endif
void operator=(const Task< T > &t)
TaskResult(const TaskResult< T > &)=delete
void wait_and_move_result() const
const T & value() const
std::optional< Task< T > > task
std::optional< T > task_result
TaskResult(const Task< T > &task)
std::atomic< bool > result_is_available
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:502
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
#define Assert(cond, exc)
static ::ExceptionBase & ExcMessage(std::string arg1)