├── .gitignore ├── LICENSE.md ├── README.md ├── b ├── dispatch_queue.cpp ├── dispatch_queue.h └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | dq_test 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cpp_dispatch_queue 2 | ================== 3 | 4 | A minimal C++11 implementation of some of Grand Central Dispatch's "dispatch_queue_t" 5 | -------------------------------------------------------------------------------- /b: -------------------------------------------------------------------------------- 1 | clang++ -std=c++11 -Weverything -pedantic -Wno-c++98-compat -Wno-padded -Wno-missing-prototypes -O3 main.cpp dispatch_queue.cpp -o dq_test 2 | 3 | -------------------------------------------------------------------------------- /dispatch_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "dispatch_queue.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using time_point = std::chrono::steady_clock::time_point; 14 | 15 | struct work_entry { 16 | explicit work_entry(std::function func_) 17 | : func(std::move(func_)), expiry(time_point()), from_timer(false) {} 18 | 19 | work_entry(std::function func_, time_point expiry_) 20 | : func(std::move(func_)), expiry(expiry_), from_timer(true) {} 21 | 22 | std::function func; 23 | time_point expiry; 24 | bool from_timer; 25 | }; 26 | 27 | bool operator>(work_entry const &lhs, work_entry const &rhs) { 28 | return lhs.expiry > rhs.expiry; 29 | } 30 | 31 | struct dispatch_queue::impl { 32 | impl(); 33 | static void dispatch_thread_proc(impl *self); 34 | static void timer_thread_proc(impl *self); 35 | 36 | std::mutex work_queue_mtx; 37 | std::condition_variable work_queue_cond; 38 | std::deque work_queue; 39 | 40 | std::mutex timer_mtx; 41 | std::condition_variable timer_cond; 42 | std::priority_queue, 43 | std::greater > 44 | timers; 45 | 46 | std::thread work_queue_thread; 47 | std::thread timer_thread; 48 | 49 | std::atomic quit; 50 | std::atomic work_queue_thread_started; 51 | std::atomic timer_thread_started; 52 | 53 | using work_queue_lock = std::unique_lock; 54 | using timer_lock = std::unique_lock; 55 | }; 56 | 57 | void dispatch_queue::impl::dispatch_thread_proc(dispatch_queue::impl *self) { 58 | work_queue_lock work_queue_lock(self->work_queue_mtx); 59 | self->work_queue_cond.notify_one(); 60 | self->work_queue_thread_started = true; 61 | 62 | while (self->quit == false) { 63 | self->work_queue_cond.wait(work_queue_lock, 64 | [&] { return !self->work_queue.empty(); }); 65 | 66 | while (!self->work_queue.empty()) { 67 | auto work = self->work_queue.back(); 68 | self->work_queue.pop_back(); 69 | 70 | work_queue_lock.unlock(); 71 | work.func(); 72 | work_queue_lock.lock(); 73 | } 74 | } 75 | } 76 | 77 | void dispatch_queue::impl::timer_thread_proc(dispatch_queue::impl *self) { 78 | timer_lock timer_lock(self->timer_mtx); 79 | self->timer_cond.notify_one(); 80 | self->timer_thread_started = true; 81 | 82 | while (self->quit == false) { 83 | if (self->timers.empty()) { 84 | self->timer_cond.wait(timer_lock, [&] { 85 | return self->quit || !self->timers.empty(); 86 | }); 87 | } 88 | 89 | while (!self->timers.empty()) { 90 | auto const &work = self->timers.top(); 91 | if (self->timer_cond.wait_until(timer_lock, work.expiry, [&] { 92 | return self->quit.load(); 93 | })) { 94 | break; 95 | } 96 | 97 | { 98 | work_queue_lock _(self->work_queue_mtx); 99 | auto where = std::find_if( 100 | self->work_queue.rbegin(), self->work_queue.rend(), 101 | [](work_entry const &w) { return !w.from_timer; }); 102 | self->work_queue.insert(where.base(), work); 103 | self->timers.pop(); 104 | self->work_queue_cond.notify_one(); 105 | } 106 | } 107 | } 108 | } 109 | 110 | dispatch_queue::impl::impl() 111 | : quit(false), 112 | work_queue_thread_started(false), 113 | timer_thread_started(false) { 114 | work_queue_lock work_queue_lock(work_queue_mtx); 115 | timer_lock timer_lock(timer_mtx); 116 | 117 | work_queue_thread = std::thread(dispatch_thread_proc, this); 118 | timer_thread = std::thread(timer_thread_proc, this); 119 | 120 | work_queue_cond.wait(work_queue_lock, 121 | [this] { return work_queue_thread_started.load(); }); 122 | timer_cond.wait(timer_lock, [this] { return timer_thread_started.load(); }); 123 | } 124 | 125 | dispatch_queue::dispatch_queue() : m(new impl) {} 126 | 127 | dispatch_queue::~dispatch_queue() { 128 | dispatch_async([this] { m->quit = true; }); 129 | m->work_queue_thread.join(); 130 | 131 | { 132 | impl::timer_lock _(m->timer_mtx); 133 | m->timer_cond.notify_one(); 134 | } 135 | 136 | m->timer_thread.join(); 137 | } 138 | 139 | void dispatch_queue::dispatch_async(std::function func) { 140 | impl::work_queue_lock _(m->work_queue_mtx); 141 | m->work_queue.push_front(work_entry(func)); 142 | m->work_queue_cond.notify_one(); 143 | } 144 | 145 | void dispatch_queue::dispatch_sync(std::function func) { 146 | std::mutex sync_mtx; 147 | impl::work_queue_lock work_queue_lock(sync_mtx); 148 | std::condition_variable sync_cond; 149 | std::atomic completed(false); 150 | 151 | { 152 | impl::work_queue_lock _(m->work_queue_mtx); 153 | m->work_queue.push_front(work_entry(func)); 154 | m->work_queue.push_front(work_entry([&] { 155 | std::unique_lock sync_cb_lock(sync_mtx); 156 | completed = true; 157 | sync_cond.notify_one(); 158 | })); 159 | 160 | m->work_queue_cond.notify_one(); 161 | } 162 | 163 | sync_cond.wait(work_queue_lock, [&] { return completed.load(); }); 164 | } 165 | 166 | void dispatch_queue::dispatch_after(int msec, std::function func) { 167 | impl::timer_lock _(m->timer_mtx); 168 | m->timers.push(work_entry(func, std::chrono::steady_clock::now() + 169 | std::chrono::milliseconds(msec))); 170 | m->timer_cond.notify_one(); 171 | } 172 | 173 | void dispatch_queue::dispatch_flush() { 174 | dispatch_sync([] {}); 175 | } 176 | 177 | -------------------------------------------------------------------------------- /dispatch_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class dispatch_queue { 6 | public: 7 | dispatch_queue(); 8 | ~dispatch_queue(); 9 | 10 | void dispatch_async(std::function func); 11 | void dispatch_sync(std::function func); 12 | void dispatch_after(int msec, std::function func); 13 | void dispatch_flush(); 14 | 15 | dispatch_queue(dispatch_queue const&) = delete; 16 | dispatch_queue& operator=(dispatch_queue const&) = delete; 17 | 18 | private: 19 | struct impl; 20 | std::unique_ptr m; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "dispatch_queue.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace { 8 | void hello() { std::printf("hello world\n"); } 9 | void add(int a, int b) { std::printf("%d + %d = %d\n", a, b, a + b); } 10 | } // namespace 11 | 12 | int main(void) { 13 | dispatch_queue dq; 14 | 15 | for (auto i = 0; i < 20; ++i) { 16 | dq.dispatch_after(i * 50, 17 | [=] { std::printf("dispatch_after(%d)\n", i * 50); }); 18 | } 19 | 20 | dq.dispatch_after(5, [] { std::printf("explicit dispatch_after(5)\n"); }); 21 | dq.dispatch_after(300, 22 | [] { std::printf("explicit dispatch_after(300)\n"); }); 23 | 24 | dq.dispatch_async(hello); 25 | dq.dispatch_async(std::bind(add, 123, 456)); 26 | 27 | std::printf("sleeping main thread for 500ms...\n"); 28 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 29 | std::printf("main thread waking up.\n"); 30 | 31 | { 32 | auto bound_counter = 0; 33 | for (auto i = 0; i < 1024; ++i) { 34 | dq.dispatch_async([&] { ++bound_counter; }); 35 | } 36 | 37 | dq.dispatch_flush(); 38 | std::printf("bound_counter = %d\n", bound_counter); 39 | } 40 | 41 | std::printf("dispatch_sync sleep for 1s...\n"); 42 | std::fflush(stdout); 43 | dq.dispatch_sync( 44 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); }); 45 | std::printf("dispatch_sync sleep complete.\n"); 46 | 47 | auto nest = 0; 48 | { 49 | dq.dispatch_async([&] { 50 | std::printf("dispatch_async: nesting level %d\n", nest++); 51 | dq.dispatch_async([&] { 52 | std::printf("dispatch_async: nesting level %d\n", nest++); 53 | dq.dispatch_async([&] { 54 | std::printf("dispatch_async: nesting level %d\n", nest++); 55 | }); 56 | }); 57 | }); 58 | } 59 | 60 | { 61 | auto param_counter = 0; 62 | for (auto i = 0; i < 2048; ++i) { 63 | dq.dispatch_async( 64 | std::bind([](int& c) { ++c; }, std::ref(param_counter))); 65 | } 66 | 67 | dq.dispatch_flush(); 68 | std::printf("param_counter = %d\n", param_counter); 69 | } 70 | 71 | return 0; 72 | } 73 | 74 | --------------------------------------------------------------------------------