├── .gitignore ├── LICENSE ├── README.md ├── TaskQueue.pro ├── event.cpp ├── event.h ├── example.cpp ├── queued_task.h ├── task_queue.cpp ├── task_queue.h ├── task_queue_base.cpp ├── task_queue_base.h ├── task_queue_manager.cpp ├── task_queue_manager.h ├── task_queue_std.cpp └── task_queue_std.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.pro.user 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ouxianghui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # task-queue 2 | 3 | Basic usage 4 | ---- 5 | ```C++ 6 | 7 | #include 8 | #include "task_queue.h" 9 | #include 10 | #include 11 | #include "event.h" 12 | #include "task_queue_manager.h" 13 | 14 | using namespace std; 15 | 16 | int main() 17 | { 18 | cout << "Hello World!" << endl; 19 | 20 | TQMgr->create({"worker1", "worker2", "worker3"}); 21 | 22 | vi::Event ev; 23 | 24 | const int N = 12; 25 | std::thread threads[N]; 26 | for (int n = 0; n < N; ++n) { 27 | threads[n] = std::thread([&ev]{ 28 | for (int i = 0; i < 10000; ++i) { 29 | TQ("worker1")->postTask([&ev, i](){ 30 | cout << "exec task in 'worker1' queue: " << ", i = " << i << endl; 31 | }); 32 | 33 | TQ("worker1")->postDelayedTask([&ev, i](){ 34 | cout << "exec delayed task in 'worker1' queue: " << ", i = " << i << endl; 35 | }, 1000); 36 | 37 | TQ("worker2")->postDelayedTask([&ev, i](){ 38 | cout << "exec delayed task in 'worker2' queue: " << ", i = " << i << endl; 39 | if (i == 9999) { 40 | ev.set(); 41 | } 42 | }, 1000); 43 | } 44 | }); 45 | } 46 | 47 | ev.wait(-1); 48 | for (int n = 0; n < N; ++n) { 49 | threads[n].join(); 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /TaskQueue.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console c++17 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | 6 | SOURCES += \ 7 | event.cpp \ 8 | example.cpp \ 9 | task_queue.cpp \ 10 | task_queue_base.cpp \ 11 | task_queue_manager.cpp \ 12 | task_queue_std.cpp 13 | 14 | HEADERS += \ 15 | event.h \ 16 | queued_task.h \ 17 | task_queue.h \ 18 | task_queue_base.h \ 19 | task_queue_manager.h \ 20 | task_queue_std.h 21 | -------------------------------------------------------------------------------- /event.cpp: -------------------------------------------------------------------------------- 1 | #include "event.h" 2 | #include 3 | 4 | #ifdef _WIN32 5 | #include 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | namespace vi { 12 | 13 | Event::Event() : Event(false, false) { 14 | 15 | } 16 | 17 | Event::Event(bool manual_reset, bool initially_signaled) 18 | : is_manual_reset_(manual_reset), event_status_(initially_signaled) { 19 | 20 | } 21 | 22 | Event::~Event() { 23 | } 24 | 25 | void Event::set() { 26 | std::unique_lock(event_mutex_); 27 | event_status_ = true; 28 | event_cond_.notify_all(); 29 | } 30 | 31 | void Event::reset() { 32 | std::unique_lock(event_mutex_); 33 | event_status_ = false; 34 | } 35 | 36 | namespace { 37 | 38 | #ifdef _WIN32 39 | static void gettimeofday(struct timeval *tv, void *ignore) 40 | { 41 | struct timeb tb; 42 | 43 | ftime(&tb); 44 | tv->tv_sec = (long)tb.time; 45 | tv->tv_usec = tb.millitm * 1000; 46 | } 47 | #endif 48 | 49 | timespec getTimespec(const int milliseconds_from_now) { 50 | timespec ts; 51 | 52 | // Get the current time. 53 | #if USE_CLOCK_GETTIME 54 | clock_gettime(CLOCK_MONOTONIC, &ts); 55 | #else 56 | timeval tv; 57 | gettimeofday(&tv, nullptr); 58 | ts.tv_sec = tv.tv_sec; 59 | ts.tv_nsec = tv.tv_usec * 1000; 60 | #endif 61 | 62 | // Add the specified number of milliseconds to it. 63 | ts.tv_sec += (milliseconds_from_now / 1000); 64 | ts.tv_nsec += (milliseconds_from_now % 1000) * 1000000; 65 | 66 | // Normalize. 67 | if (ts.tv_nsec >= 1000000000) { 68 | ts.tv_sec++; 69 | ts.tv_nsec -= 1000000000; 70 | } 71 | 72 | return ts; 73 | } 74 | 75 | } // namespace 76 | 77 | bool Event::wait(const int give_up_after_ms, const int warn_after_ms) { 78 | // Instant when we'll log a warning message (because we've been waiting so 79 | // long it might be a bug), but not yet give up waiting. nullopt if we 80 | // shouldn't log a warning. 81 | const std::optional warn_ts = warn_after_ms == kForever || 82 | (give_up_after_ms != kForever && warn_after_ms > give_up_after_ms) 83 | ? std::nullopt 84 | : std::make_optional(getTimespec(warn_after_ms)); 85 | 86 | // Instant when we'll stop waiting and return an error. nullopt if we should 87 | // never give up. 88 | const std::optional give_up_ts = 89 | give_up_after_ms == kForever 90 | ? std::nullopt 91 | : std::make_optional(getTimespec(give_up_after_ms)); 92 | 93 | //ScopedYieldPolicy::YieldExecution(); 94 | 95 | std::unique_lock lock(event_mutex_); 96 | 97 | // Wait for `event_cond_` to trigger and `event_status_` to be set, with the 98 | // given timeout (or without a timeout if none is given). 99 | const auto wait = [&](const std::optional timeout_ts) { 100 | std::cv_status status = std::cv_status::no_timeout; 101 | while (!event_status_ && status == std::cv_status::no_timeout) { 102 | if (timeout_ts == std::nullopt) { 103 | event_cond_.wait(lock); 104 | } else { 105 | using namespace std::chrono; 106 | system_clock::time_point tp{duration_cast(seconds{timeout_ts->tv_sec} + nanoseconds{(timeout_ts->tv_nsec)})}; 107 | status = event_cond_.wait_until(lock, tp); 108 | } 109 | } 110 | return status; 111 | }; 112 | 113 | std::cv_status error; 114 | if (warn_ts == std::nullopt) { 115 | error = wait(give_up_ts); 116 | } else { 117 | error = wait(warn_ts); 118 | if (error == std::cv_status::timeout) { 119 | error = wait(give_up_ts); 120 | } 121 | } 122 | 123 | // NOTE(liulk): Exactly one thread will auto-reset this event. All 124 | // the other threads will think it's unsignaled. This seems to be 125 | // consistent with auto-reset events in WEBRTC_WIN 126 | if (error == std::cv_status::no_timeout && !is_manual_reset_) { 127 | event_status_ = false; 128 | } 129 | 130 | return (error == std::cv_status::no_timeout); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vi { 7 | 8 | class Event { 9 | public: 10 | static const int kForever = -1; 11 | 12 | Event(); 13 | Event(bool manual_reset, bool initially_signaled); 14 | ~Event(); 15 | 16 | void set(); 17 | void reset(); 18 | 19 | // Waits for the event to become signaled, but logs a warning if it takes more 20 | // than `warn_after_ms` milliseconds, and gives up completely if it takes more 21 | // than `give_up_after_ms` milliseconds. (If `warn_after_ms >= 22 | // give_up_after_ms`, no warning will be logged.) Either or both may be 23 | // `kForever`, which means wait indefinitely. 24 | // 25 | // Returns true if the event was signaled, false if there was a timeout or 26 | // some other error. 27 | bool wait(int give_up_after_ms, int warn_after_ms); 28 | 29 | // Waits with the given timeout and a reasonable default warning timeout. 30 | bool wait(int give_up_after_ms) { 31 | return wait(give_up_after_ms, give_up_after_ms == kForever ? 3000 : kForever); 32 | } 33 | 34 | private: 35 | Event(const Event&) = delete; 36 | Event& operator=(const Event&) = delete; 37 | 38 | private: 39 | std::mutex event_mutex_; 40 | std::condition_variable event_cond_; 41 | const bool is_manual_reset_; 42 | bool event_status_; 43 | }; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "task_queue.h" 3 | #include 4 | #include 5 | #include "event.h" 6 | #include "task_queue_manager.h" 7 | 8 | using namespace std; 9 | 10 | int main() 11 | { 12 | cout << "Hello World!" << endl; 13 | 14 | TQMgr->create({"worker1", "worker2", "worker3"}); 15 | 16 | vi::Event ev; 17 | 18 | const int N = 12; 19 | std::thread threads[N]; 20 | for (int n = 0; n < N; ++n) { 21 | threads[n] = std::thread([&ev]{ 22 | for (int i = 0; i < 10000; ++i) { 23 | TQ("worker1")->postTask([&ev, i](){ 24 | cout << "exec task in 'worker1' queue: " << ", i = " << i << endl; 25 | }); 26 | 27 | TQ("worker1")->postDelayedTask([&ev, i](){ 28 | cout << "exec delayed task in 'worker1' queue: " << ", i = " << i << endl; 29 | }, 1000); 30 | 31 | TQ("worker2")->postDelayedTask([&ev, i](){ 32 | cout << "exec delayed task in 'worker2' queue: " << ", i = " << i << endl; 33 | if (i == 9999) { 34 | ev.set(); 35 | } 36 | }, 1000); 37 | } 38 | }); 39 | } 40 | 41 | ev.wait(-1); 42 | for (int n = 0; n < N; ++n) { 43 | threads[n].join(); 44 | } 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /queued_task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vi { 7 | 8 | // Base interface for asynchronously executed tasks. 9 | // The interface basically consists of a single function, run(), that executes 10 | // on the target queue. For more details see the run() method and TaskQueue. 11 | class QueuedTask { 12 | public: 13 | virtual ~QueuedTask() = default; 14 | 15 | // Main routine that will run when the task is executed on the desired queue. 16 | // The task should return |true| to indicate that it should be deleted or 17 | // |false| to indicate that the queue should consider ownership of the task 18 | // having been transferred. Returning |false| can be useful if a task has 19 | // re-posted itself to a different queue or is otherwise being re-used. 20 | virtual bool run() = 0; 21 | }; 22 | 23 | // Simple implementation of QueuedTask for use with rtc::Bind and lambdas. 24 | template 25 | class ClosureTask : public QueuedTask { 26 | public: 27 | explicit ClosureTask(Closure&& closure) 28 | : closure_(std::forward(closure)) {} 29 | 30 | private: 31 | bool run() override { 32 | closure_(); 33 | return true; 34 | } 35 | 36 | typename std::decay::type closure_; 37 | }; 38 | 39 | template 40 | std::unique_ptr ToQueuedTask(Closure&& closure) { 41 | return std::make_unique>(std::forward(closure)); 42 | } 43 | 44 | // Extends ClosureTask to also allow specifying cleanup code. 45 | // This is useful when using lambdas if guaranteeing cleanup, even if a task 46 | // was dropped (queue is too full), is required. 47 | template 48 | class ClosureTaskWithCleanup : public ClosureTask { 49 | public: 50 | ClosureTaskWithCleanup(Closure&& closure, Cleanup&& cleanup) 51 | : ClosureTask(std::forward(closure)) 52 | , cleanup_(std::forward(cleanup)) {} 53 | ~ClosureTaskWithCleanup() override { cleanup_(); } 54 | 55 | private: 56 | typename std::decay::type cleanup_; 57 | }; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /task_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "task_queue.h" 2 | #include "task_queue_base.h" 3 | #include "task_queue_std.h" 4 | 5 | namespace vi { 6 | 7 | TaskQueue::TaskQueue(std::unique_ptr taskQueue) 8 | : impl_(taskQueue.release()) {} 9 | 10 | TaskQueue::~TaskQueue() { 11 | // There might running task that tries to rescheduler itself to the TaskQueue 12 | // and not yet aware TaskQueue destructor is called. 13 | // Calling back to TaskQueue::PostTask need impl_ pointer still be valid, so 14 | // do not invalidate impl_ pointer until Delete returns. 15 | impl_->deleteThis(); 16 | } 17 | 18 | bool TaskQueue::isCurrent() const { 19 | return impl_->isCurrent(); 20 | } 21 | 22 | void TaskQueue::postTask(std::unique_ptr task) { 23 | return impl_->postTask(std::move(task)); 24 | } 25 | 26 | void TaskQueue::postDelayedTask(std::unique_ptr task, uint32_t milliseconds) { 27 | return impl_->postDelayedTask(std::move(task), milliseconds); 28 | } 29 | 30 | std::unique_ptr TaskQueue::create(std::string_view name) { 31 | return std::make_unique(std::unique_ptr(new TaskQueueSTD(name))); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /task_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include "queued_task.h" 8 | 9 | 10 | namespace vi { 11 | // Implements a task queue that asynchronously executes tasks in a way that 12 | // guarantees that they're executed in FIFO order and that tasks never overlap. 13 | // Tasks may always execute on the same worker thread and they may not. 14 | // To DCHECK that tasks are executing on a known task queue, use IsCurrent(). 15 | // 16 | // Here are some usage examples: 17 | // 18 | // 1) Asynchronously running a lambda: 19 | // 20 | // class MyClass { 21 | // ... 22 | // TaskQueue queue_("MyQueue"); 23 | // }; 24 | // 25 | // void MyClass::StartWork() { 26 | // queue_.PostTask([]() { Work(); }); 27 | // ... 28 | // 29 | // 2) Posting a custom task on a timer. The task posts itself again after 30 | // every running: 31 | // 32 | // class TimerTask : public QueuedTask { 33 | // public: 34 | // TimerTask() {} 35 | // private: 36 | // bool Run() override { 37 | // ++count_; 38 | // TaskQueueBase::Current()->PostDelayedTask( 39 | // absl::WrapUnique(this), 1000); 40 | // // Ownership has been transferred to the next occurance, 41 | // // so return false to prevent from being deleted now. 42 | // return false; 43 | // } 44 | // int count_ = 0; 45 | // }; 46 | // ... 47 | // queue_.PostDelayedTask(std::make_unique(), 1000); 48 | // 49 | // For more examples, see task_queue_unittests.cc. 50 | // 51 | // A note on destruction: 52 | // 53 | 54 | class TaskQueueBase; 55 | class TaskQueueDeleter; 56 | 57 | // When a TaskQueue is deleted, pending tasks will not be executed but they will 58 | // be deleted. The deletion of tasks may happen asynchronously after the 59 | // TaskQueue itself has been deleted or it may happen synchronously while the 60 | // TaskQueue instance is being deleted. This may vary from one OS to the next 61 | // so assumptions about lifetimes of pending tasks should not be made. 62 | class TaskQueue { 63 | public: 64 | explicit TaskQueue(std::unique_ptr taskQueue); 65 | ~TaskQueue(); 66 | 67 | static std::unique_ptr create(std::string_view name); 68 | 69 | // Used for DCHECKing the current queue. 70 | bool isCurrent() const; 71 | 72 | // Returns non-owning pointer to the task queue implementation. 73 | TaskQueueBase* get() { return impl_; } 74 | 75 | // TODO(tommi): For better debuggability, implement RTC_FROM_HERE. 76 | 77 | // Ownership of the task is passed to PostTask. 78 | void postTask(std::unique_ptr task); 79 | 80 | // Schedules a task to execute a specified number of milliseconds from when 81 | // the call is made. The precision should be considered as "best effort" 82 | // and in some cases, such as on Windows when all high precision timers have 83 | // been used up, can be off by as much as 15 millseconds (although 8 would be 84 | // more likely). This can be mitigated by limiting the use of delayed tasks. 85 | void postDelayedTask(std::unique_ptr task, uint32_t milliseconds); 86 | 87 | 88 | // std::enable_if is used here to make sure that calls to PostTask() with 89 | // std::unique_ptr would not end up being 90 | // caught by this template. 91 | template >::value>::type* = nullptr> 92 | void postTask(Closure&& closure) { 93 | postTask(ToQueuedTask(std::forward(closure))); 94 | } 95 | 96 | // See documentation above for performance expectations. 97 | template >::value>::type* = nullptr> 98 | void postDelayedTask(Closure&& closure, uint32_t milliseconds) { 99 | postDelayedTask(ToQueuedTask(std::forward(closure)), milliseconds); 100 | } 101 | 102 | 103 | private: 104 | TaskQueue& operator=(const TaskQueue&) = delete; 105 | TaskQueue(const TaskQueue&) = delete; 106 | 107 | private: 108 | TaskQueueBase* const impl_; 109 | 110 | }; 111 | 112 | } 113 | -------------------------------------------------------------------------------- /task_queue_base.cpp: -------------------------------------------------------------------------------- 1 | #include "task_queue_base.h" 2 | #include 3 | 4 | namespace vi { 5 | 6 | namespace { 7 | 8 | thread_local TaskQueueBase* _current = nullptr; 9 | 10 | } // namespace 11 | 12 | TaskQueueBase* TaskQueueBase::current() { 13 | return _current; 14 | } 15 | 16 | TaskQueueBase::CurrentTaskQueueSetter::CurrentTaskQueueSetter(TaskQueueBase* taskQueue) 17 | : _previous(_current) { 18 | _current = taskQueue; 19 | } 20 | 21 | TaskQueueBase::CurrentTaskQueueSetter::~CurrentTaskQueueSetter() { 22 | _current = _previous; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /task_queue_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "queued_task.h" 6 | 7 | namespace vi { 8 | 9 | // Asynchronously executes tasks in a way that guarantees that they're executed 10 | // in FIFO order and that tasks never overlap. Tasks may always execute on the 11 | // same worker thread and they may not. To DCHECK that tasks are executing on a 12 | // known task queue, use IsCurrent(). 13 | class TaskQueueBase { 14 | public: 15 | // Starts destruction of the task queue. 16 | // On return ensures no task are running and no new tasks are able to start 17 | // on the task queue. 18 | // Responsible for deallocation. Deallocation may happen syncrhoniously during 19 | // Delete or asynchronously after Delete returns. 20 | // Code not running on the TaskQueue should not make any assumption when 21 | // TaskQueue is deallocated and thus should not call any methods after Delete. 22 | // Code running on the TaskQueue should not call Delete, but can assume 23 | // TaskQueue still exists and may call other methods, e.g. PostTask. 24 | virtual void deleteThis() = 0; 25 | 26 | // Schedules a task to execute. Tasks are executed in FIFO order. 27 | // If |task->Run()| returns true, task is deleted on the task queue 28 | // before next QueuedTask starts executing. 29 | // When a TaskQueue is deleted, pending tasks will not be executed but they 30 | // will be deleted. The deletion of tasks may happen synchronously on the 31 | // TaskQueue or it may happen asynchronously after TaskQueue is deleted. 32 | // This may vary from one implementation to the next so assumptions about 33 | // lifetimes of pending tasks should not be made. 34 | virtual void postTask(std::unique_ptr task) = 0; 35 | 36 | // Schedules a task to execute a specified number of milliseconds from when 37 | // the call is made. The precision should be considered as "best effort" 38 | // and in some cases, such as on Windows when all high precision timers have 39 | // been used up, can be off by as much as 15 millseconds. 40 | virtual void postDelayedTask(std::unique_ptr task, uint32_t milliseconds) = 0; 41 | 42 | // Returns the task queue that is running the current thread. 43 | // Returns nullptr if this thread is not associated with any task queue. 44 | static TaskQueueBase* current(); 45 | bool isCurrent() const { return current() == this; } 46 | 47 | virtual const std::string& name() const = 0; 48 | 49 | protected: 50 | class CurrentTaskQueueSetter { 51 | public: 52 | explicit CurrentTaskQueueSetter(TaskQueueBase* taskQueue); 53 | CurrentTaskQueueSetter(const CurrentTaskQueueSetter&) = delete; 54 | CurrentTaskQueueSetter& operator=(const CurrentTaskQueueSetter&) = delete; 55 | ~CurrentTaskQueueSetter(); 56 | 57 | private: 58 | TaskQueueBase* const _previous; 59 | }; 60 | 61 | // Users of the TaskQueue should call Delete instead of directly deleting 62 | // this object. 63 | virtual ~TaskQueueBase() = default; 64 | }; 65 | 66 | struct TaskQueueDeleter { 67 | void operator()(TaskQueueBase* taskQueue) const { taskQueue->deleteThis(); } 68 | }; 69 | 70 | } // namespace webrtc 71 | -------------------------------------------------------------------------------- /task_queue_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "task_queue_manager.h" 2 | #include "task_queue.h" 3 | 4 | namespace vi { 5 | 6 | std::unique_ptr& TaskQueueManager::instance() 7 | { 8 | static std::unique_ptr _instance = nullptr; 9 | static std::once_flag ocf; 10 | std::call_once(ocf, [](){ 11 | _instance.reset(new TaskQueueManager()); 12 | }); 13 | return _instance; 14 | } 15 | 16 | TaskQueueManager::TaskQueueManager() 17 | { 18 | 19 | } 20 | 21 | TaskQueueManager::~TaskQueueManager() 22 | { 23 | clear(); 24 | } 25 | 26 | void TaskQueueManager::create(const std::vector& nameList) 27 | { 28 | std::unique_lock lock(m_mutex); 29 | for (const auto& name : nameList) { 30 | if (!exist(name)) { 31 | m_queueMap[name] = TaskQueue::create(name); 32 | } 33 | } 34 | } 35 | 36 | void TaskQueueManager::clear() 37 | { 38 | std::unique_lock lock(m_mutex); 39 | m_queueMap.clear(); 40 | } 41 | 42 | bool TaskQueueManager::exist(const std::string& name) 43 | { 44 | return (m_queueMap.find(name) != m_queueMap.end()); 45 | } 46 | 47 | bool TaskQueueManager::hasQueue(const std::string& name) 48 | { 49 | std::unique_lock lock(m_mutex); 50 | return exist(name); 51 | } 52 | 53 | TaskQueue* TaskQueueManager::queue(const std::string& name) 54 | { 55 | std::unique_lock lock(m_mutex); 56 | return exist(name) ? m_queueMap[name].get() : nullptr; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /task_queue_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vi { 10 | 11 | class TaskQueue; 12 | 13 | class TaskQueueManager { 14 | public: 15 | static std::unique_ptr& instance(); 16 | 17 | ~TaskQueueManager(); 18 | 19 | void create(const std::vector& nameList); 20 | 21 | TaskQueue* queue(const std::string& name); 22 | 23 | bool hasQueue(const std::string& name); 24 | 25 | private: 26 | void clear(); 27 | 28 | bool exist(const std::string& name); 29 | 30 | private: 31 | TaskQueueManager(); 32 | 33 | TaskQueueManager(TaskQueueManager&&) = delete; 34 | 35 | TaskQueueManager(const TaskQueueManager&) = delete; 36 | 37 | TaskQueueManager& operator=(const TaskQueueManager&) = delete; 38 | 39 | private: 40 | std::mutex m_mutex; 41 | 42 | std::unordered_map> m_queueMap; 43 | 44 | }; 45 | 46 | } 47 | 48 | #define TQMgr vi::TaskQueueManager::instance() 49 | 50 | #define TQ(name) TQMgr->queue(name) 51 | -------------------------------------------------------------------------------- /task_queue_std.cpp: -------------------------------------------------------------------------------- 1 | #include "task_queue_std.h" 2 | #include 3 | 4 | namespace vi { 5 | 6 | TaskQueueSTD::TaskQueueSTD(std::string_view queueName) 7 | : started_(/*manual_reset=*/false, /*initially_signaled=*/false) 8 | , stopped_(/*manual_reset=*/false, /*initially_signaled=*/false) 9 | , flag_notify_(/*manual_reset=*/false, /*initially_signaled=*/false) 10 | , name_(queueName) { 11 | 12 | thread_ = std::thread([this]{ 13 | CurrentTaskQueueSetter setCurrent(this); 14 | this->processTasks(); 15 | }); 16 | 17 | 18 | started_.wait(vi::Event::kForever); 19 | } 20 | 21 | void TaskQueueSTD::deleteThis() { 22 | //RTC_DCHECK(!isCurrent()); 23 | assert(isCurrent() == false); 24 | 25 | { 26 | std::unique_lock lock(pending_mutex_); 27 | thread_should_quit_ = true; 28 | } 29 | 30 | notifyWake(); 31 | 32 | stopped_.wait(vi::Event::kForever); 33 | 34 | if (thread_.joinable()) { 35 | thread_.join(); 36 | } 37 | delete this; 38 | } 39 | 40 | void TaskQueueSTD::postTask(std::unique_ptr task) { 41 | { 42 | std::unique_lock lock(pending_mutex_); 43 | OrderId order = thread_posting_order_++; 44 | 45 | pending_queue_.push(std::pair>(order, std::move(task))); 46 | } 47 | 48 | notifyWake(); 49 | } 50 | 51 | void TaskQueueSTD::postDelayedTask(std::unique_ptr task, uint32_t ms) { 52 | auto fire_at = milliseconds() + ms; 53 | 54 | DelayedEntryTimeout delay; 55 | delay.next_fire_at_ms_ = fire_at; 56 | 57 | { 58 | std::unique_lock lock(pending_mutex_); 59 | delay.order_ = ++thread_posting_order_; 60 | delayed_queue_[delay] = std::move(task); 61 | } 62 | 63 | notifyWake(); 64 | } 65 | 66 | TaskQueueSTD::NextTask TaskQueueSTD::getNextTask() { 67 | NextTask result{}; 68 | 69 | auto tick = milliseconds(); 70 | 71 | std::unique_lock lock(pending_mutex_); 72 | 73 | if (thread_should_quit_) { 74 | result.final_task_ = true; 75 | return result; 76 | } 77 | 78 | if (delayed_queue_.size() > 0) { 79 | auto delayed_entry = delayed_queue_.begin(); 80 | const auto& delay_info = delayed_entry->first; 81 | auto& delay_run = delayed_entry->second; 82 | if (tick >= delay_info.next_fire_at_ms_) { 83 | if (pending_queue_.size() > 0) { 84 | auto& entry = pending_queue_.front(); 85 | auto& entry_order = entry.first; 86 | auto& entry_run = entry.second; 87 | if (entry_order < delay_info.order_) { 88 | result.run_task_ = std::move(entry_run); 89 | pending_queue_.pop(); 90 | return result; 91 | } 92 | } 93 | 94 | result.run_task_ = std::move(delay_run); 95 | delayed_queue_.erase(delayed_entry); 96 | return result; 97 | } 98 | 99 | result.sleep_time_ms_ = delay_info.next_fire_at_ms_ - tick; 100 | } 101 | 102 | if (pending_queue_.size() > 0) { 103 | auto& entry = pending_queue_.front(); 104 | result.run_task_ = std::move(entry.second); 105 | pending_queue_.pop(); 106 | } 107 | 108 | return result; 109 | } 110 | 111 | void TaskQueueSTD::processTasks() { 112 | started_.set(); 113 | 114 | while (true) { 115 | auto task = getNextTask(); 116 | 117 | if (task.final_task_) { 118 | break; 119 | } 120 | 121 | if (task.run_task_) { 122 | // process entry immediately then try again 123 | QueuedTask* release_ptr = task.run_task_.release(); 124 | if (release_ptr->run()) { 125 | delete release_ptr; 126 | } 127 | // attempt to sleep again 128 | continue; 129 | } 130 | 131 | if (0 == task.sleep_time_ms_) { 132 | flag_notify_.wait(vi::Event::kForever); 133 | } 134 | else { 135 | flag_notify_.wait(task.sleep_time_ms_); 136 | } 137 | } 138 | 139 | stopped_.set(); 140 | } 141 | 142 | void TaskQueueSTD::notifyWake() { 143 | // The queue holds pending tasks to complete. Either tasks are to be 144 | // executed immediately or tasks are to be run at some future delayed time. 145 | // For immediate tasks the task queue's thread is busy running the task and 146 | // the thread will not be waiting on the flag_notify_ event. If no immediate 147 | // tasks are available but a delayed task is pending then the thread will be 148 | // waiting on flag_notify_ with a delayed time-out of the nearest timed task 149 | // to run. If no immediate or pending tasks are available, the thread will 150 | // wait on flag_notify_ until signaled that a task has been added (or the 151 | // thread to be told to shutdown). 152 | 153 | // In all cases, when a new immediate task, delayed task, or request to 154 | // shutdown the thread is added the flag_notify_ is signaled after. If the 155 | // thread was waiting then the thread will wake up immediately and re-assess 156 | // what task needs to be run next (i.e. run a task now, wait for the nearest 157 | // timed delayed task, or shutdown the thread). If the thread was not waiting 158 | // then the thread will remained signaled to wake up the next time any 159 | // attempt to wait on the flag_notify_ event occurs. 160 | 161 | // Any immediate or delayed pending task (or request to shutdown the thread) 162 | // must always be added to the queue prior to signaling flag_notify_ to wake 163 | // up the possibly sleeping thread. This prevents a race condition where the 164 | // thread is notified to wake up but the task queue's thread finds nothing to 165 | // do so it waits once again to be signaled where such a signal may never 166 | // happen. 167 | flag_notify_.set(); 168 | } 169 | 170 | int64_t TaskQueueSTD::milliseconds() { 171 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 172 | } 173 | 174 | const std::string& TaskQueueSTD::name() const { 175 | return name_; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /task_queue_std.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "queued_task.h" 12 | #include "event.h" 13 | #include "task_queue_base.h" 14 | 15 | namespace vi { 16 | 17 | class TaskQueueSTD final : public TaskQueueBase { 18 | public: 19 | TaskQueueSTD(std::string_view queueName); 20 | ~TaskQueueSTD() override = default; 21 | 22 | void deleteThis() override; 23 | 24 | void postTask(std::unique_ptr task) override; 25 | 26 | void postDelayedTask(std::unique_ptr task, uint32_t milliseconds) override; 27 | 28 | const std::string& name() const override; 29 | 30 | private: 31 | using OrderId = uint64_t; 32 | 33 | struct DelayedEntryTimeout { 34 | int64_t next_fire_at_ms_{}; 35 | OrderId order_{}; 36 | 37 | bool operator<(const DelayedEntryTimeout& o) const { 38 | return std::tie(next_fire_at_ms_, order_) < std::tie(o.next_fire_at_ms_, o.order_); 39 | } 40 | }; 41 | 42 | struct NextTask { 43 | bool final_task_{false}; 44 | std::unique_ptr run_task_; 45 | int64_t sleep_time_ms_{}; 46 | }; 47 | 48 | NextTask getNextTask(); 49 | 50 | void processTasks(); 51 | 52 | void notifyWake(); 53 | 54 | static int64_t milliseconds(); 55 | 56 | private: 57 | // Indicates if the thread has started. 58 | vi::Event started_; 59 | 60 | // Indicates if the thread has stopped. 61 | vi::Event stopped_; 62 | 63 | // Signaled whenever a new task is pending. 64 | vi::Event flag_notify_; 65 | 66 | // Contains the active worker thread assigned to processing 67 | // tasks (including delayed tasks). 68 | std::thread thread_; 69 | 70 | std::mutex pending_mutex_; 71 | 72 | // Indicates if the worker thread needs to shutdown now. 73 | bool thread_should_quit_ {false}; 74 | 75 | // Holds the next order to use for the next task to be 76 | // put into one of the pending queues. 77 | OrderId thread_posting_order_ {}; 78 | 79 | // The list of all pending tasks that need to be processed in the 80 | // FIFO queue ordering on the worker thread. 81 | std::queue>> pending_queue_; 82 | 83 | // The list of all pending tasks that need to be processed at a future 84 | // time based upon a delay. On the off change the delayed task should 85 | // happen at exactly the same time interval as another task then the 86 | // task is processed based on FIFO ordering. std::priority_queue was 87 | // considered but rejected due to its inability to extract the 88 | // std::unique_ptr out of the queue without the presence of a hack. 89 | std::map> delayed_queue_; 90 | 91 | std::string name_; 92 | 93 | }; 94 | 95 | } 96 | 97 | --------------------------------------------------------------------------------