├── .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 |
--------------------------------------------------------------------------------