A simple thread pool with a timer (mostly for learning purpose)
- c++23: it uses
std::move_only_functionwhich 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.waitdespite halting I usewait_forwith 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_forwhich 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_oneunder the lock inscheduler_test.cppto avoid data race against CV: it could be destroyed right afterexec_timeassignment whennotify_oneis being called, e.g.exec_time.has_value()already true. Mutex is unlocked (iffinishedis 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_onein 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::crefwrappers. Instead of usingstd::tupleI preferstd::ref. See Perfect forwaring and capture - For MSVC compiler: you cannot
Postmove-only callable due to bug: they afrad to break ABI. I usestd::promise - std::futurepair to emultatestd::packaged_taskbehaviour: get future and set exception on need. I probably need replacestd::packaged_tasktoo 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_VERand usestd::promise - std::futurepair instead ofstd::packaged_task. See Notes (#4)