A simple thread pool with a timer (mostly for learning purpose)
- c++23: it uses
std::move_only_function
which you can implement yourself for old standards and compilers - cmake
- gtest: added as external project downloaded via cmake
cd build
cmake .. -DDOWNLOAD_GTEST=ON -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DBUILD_EXE=OFF
cmake --build . --target install --config Debug
- To address the issue of executing
Halt()
between evaluatinghalt_
variable in this cv's predicate and cv going to sleep (see 2) which leads to undesired block oncv.wait
despite halting I usewait_for
with timeout around100ms
. UPDATE: switched back to mutex for queue because we're locking mutex when pushing callback anyway so using atomic variable won't give any speedup: I feel like approach withwait_for
which will have to spin in the loop to check whether it's timeout occured or new work appeared cost more CPU cycles then locking mutex! - Use
notify_one
under the lock inscheduler_test.cpp
to avoid data race against CV: it could be destroyed right afterexec_time
assignment whennotify_one
is being called, e.g.exec_time.has_value()
already true. Mutex is unlocked (iffinished
is not under the lock). At the same time CV wakes up, checks stop predicate and doesn't wait anymore! Thread with CV can THERIOTICALLY be destroyed before/whennotify_one
in another thread being called. To resolve this you can either increase lifetime of CV (shared_ptr, static, etc) or notify under lockedmutex
. See pthread_cond_signal - For passing references I decide to pass args using
std::ref/std::cref
wrappers. Instead of usingstd::tuple
I preferstd::ref
. See Perfect forwaring and capture - For MSVC compiler: you cannot
Post
move-only callable due to bug: they afrad to break ABI. I usestd::promise - std::future
pair to emultatestd::packaged_task
behaviour: get future and set exception on need. I probably need replacestd::packaged_task
too and leave only workaround for MSVC but I don't want for now... Just leave it to the future ME. For bug issue see MSVC (5).
// Notes#2 ...
scheduler.ScheduleAt(expectedExecTime, [&]() {
// { with this scope uncommented data race will occur
std::lock_guard lock{ mutex };
exec_time.emplace(std::chrono::steady_clock::now());
// }
finished.notify_one();
});
std::unique_lock lock{ mutex };
(void) finished.wait_for(lock, maxWaitTime * 2, [&]() {
return exec_time.has_value();
});
- decide whether to get rid of
_MSC_VER
and usestd::promise - std::future
pair instead ofstd::packaged_task
. See Notes (#4)