1 //===-- llvm/Support/TaskQueue.h - A TaskQueue implementation ---*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a crude C++11 based task queue.
11 //===----------------------------------------------------------------------===//
13 #ifndef LLVM_SUPPORT_TASK_QUEUE_H
14 #define LLVM_SUPPORT_TASK_QUEUE_H
16 #include "llvm/Config/llvm-config.h"
17 #include "llvm/Support/ThreadPool.h"
18 #include "llvm/Support/thread.h"
22 #include <condition_variable>
31 /// TaskQueue executes serialized work on a user-defined Thread Pool. It
32 /// guarantees that if task B is enqueued after task A, task B begins after
33 /// task A completes and there is no overlap between the two.
35 // Because we don't have init capture to use move-only local variables that
36 // are captured into a lambda, we create the promise inside an explicit
37 // callable struct. We want to do as much of the wrapping in the
38 // type-specialized domain (before type erasure) and then erase this into a
40 template <typename Callable
> struct Task
{
41 using ResultTy
= typename
std::result_of
<Callable()>::type
;
42 explicit Task(Callable C
, TaskQueue
&Parent
)
43 : C(std::move(C
)), P(std::make_shared
<std::promise
<ResultTy
>>()),
47 void invokeCallbackAndSetPromise(T
*) {
51 void invokeCallbackAndSetPromise(void*) {
56 void operator()() noexcept
{
57 ResultTy
*Dummy
= nullptr;
58 invokeCallbackAndSetPromise(Dummy
);
59 Parent
->completeTask();
63 std::shared_ptr
<std::promise
<ResultTy
>> P
;
68 /// Construct a task queue with no work.
69 TaskQueue(ThreadPool
&Scheduler
) : Scheduler(Scheduler
) { (void)Scheduler
; }
71 /// Blocking destructor: the queue will wait for all work to complete.
74 assert(Tasks
.empty());
77 /// Asynchronous submission of a task to the queue. The returned future can be
78 /// used to wait for the task (and all previous tasks that have not yet
79 /// completed) to finish.
80 template <typename Callable
>
81 std::future
<typename
std::result_of
<Callable()>::type
> async(Callable
&&C
) {
82 #if !LLVM_ENABLE_THREADS
84 "TaskQueue requires building with LLVM_ENABLE_THREADS!");
86 Task
<Callable
> T
{std::move(C
), *this};
87 using ResultTy
= typename
std::result_of
<Callable()>::type
;
88 std::future
<ResultTy
> F
= T
.P
->get_future();
90 std::lock_guard
<std::mutex
> Lock(QueueLock
);
91 // If there's already a task in flight, just queue this one up. If
92 // there is not a task in flight, bypass the queue and schedule this
95 Tasks
.push_back(std::move(T
));
97 Scheduler
.async(std::move(T
));
98 IsTaskInFlight
= true;
105 void completeTask() {
106 // We just completed a task. If there are no more tasks in the queue,
107 // update IsTaskInFlight to false and stop doing work. Otherwise
108 // schedule the next task (while not holding the lock).
109 std::function
<void()> Continuation
;
111 std::lock_guard
<std::mutex
> Lock(QueueLock
);
113 IsTaskInFlight
= false;
117 Continuation
= std::move(Tasks
.front());
120 Scheduler
.async(std::move(Continuation
));
123 /// The thread pool on which to run the work.
124 ThreadPool
&Scheduler
;
126 /// State which indicates whether the queue currently is currently processing
128 bool IsTaskInFlight
= false;
130 /// Mutex for synchronizing access to the Tasks array.
131 std::mutex QueueLock
;
133 /// Tasks waiting for execution in the queue.
134 std::deque
<std::function
<void()>> Tasks
;
138 #endif // LLVM_SUPPORT_TASK_QUEUE_H