From df57af410313d414ffa4d1499681727502d16326 Mon Sep 17 00:00:00 2001 From: Timo Heister Date: Mon, 3 Jul 2023 17:15:33 -0400 Subject: [PATCH] Use taskflow to create asynchronous tasks. --- include/deal.II/base/thread_management.h | 46 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/include/deal.II/base/thread_management.h b/include/deal.II/base/thread_management.h index da68b4520a..db0a9b7a60 100644 --- a/include/deal.II/base/thread_management.h +++ b/include/deal.II/base/thread_management.h @@ -23,6 +23,10 @@ #include #include +#ifdef DEAL_II_WITH_TASKFLOW +# include +#endif + #include #include #include @@ -498,7 +502,10 @@ namespace Threads { if (MultithreadInfo::n_threads() > 1) { -#ifdef DEAL_II_WITH_TBB +#ifdef DEAL_II_WITH_TASKFLOW + task_data = std::make_shared( + MultithreadInfo::get_taskflow_executor().async(function_object)); +#elif defined(DEAL_II_WITH_TBB) // Create a promise object and from it extract a future that // we can use to refer to the outcome of the task. For reasons // explained below, we can't just create a std::promise object, @@ -939,7 +946,42 @@ namespace Threads return; else { -#ifdef DEAL_II_WITH_TBB +#ifdef DEAL_II_WITH_TASKFLOW + // We want to call executor.corun_until() to keep scheduling tasks + // until the task we are waiting for has actually finished. The + // problem is that TaskFlow documents that you can only call + // corun_until() on a worker of the executor. In other words, we + // can call it from *inside* other tasks, but not from the main + // thread (or other threads that might have been created outside + // of TaskFlow). + // + // Fortunately, we can check whether we are on a worker thread: + if (MultithreadInfo::get_taskflow_executor().this_worker_id() >= 0) + MultithreadInfo::get_taskflow_executor().corun_until([this]() { + return (future.wait_for(std::chrono::seconds(0)) == + std::future_status::ready); + }); + else + // We are on a thread not managed by TaskFlow. In that case, we + // can simply stop the current thread to wait for the task to + // finish (i.e., for the std::future object to become ready). We + // can do this because we need not fear that this leads to a + // deadlock: The current threads is waiting for completion of a + // task that is running on a completely different set of + // threads, and so not making any progress here can not deprive + // these other threads of the ability to schedule their tasks. + // + // Indeed, this is even true if the current thread is a worker + // of one executor and we are waiting for a task running on a + // different executor: The current task being stopped may block + // the current executor from scheduling more tasks, but it is + // unrelated to the tasks of the scheduler for which we are + // waiting for something, and so that other executor will + // eventually get arond to scheduling the task we are waiting + // for, at which point the current task will also complete. + future.wait(); + +#elif defined(DEAL_II_WITH_TBB) // If we build on the TBB, then we can't just wait for the // std::future object to get ready. Apparently the TBB happily // enqueues a task into an arena and then just sits on it without -- 2.39.5