如何在C++中实现可重置延迟的函数调用?
实现可重置的延迟调用函数
需求说明
- 首次按键时启动3秒定时器,到期后执行目标函数
- 延迟期间再次按键,需将延迟时间重置为3秒
- 举例:首次按键后,1秒、3秒(首次+2)、4秒(首次+1)各按一次,之后停止按键,需再等待3秒才执行目标函数(从首次按键起总计7秒)
原代码问题
以下是尝试实现的代码,但无法达到预期效果——无论中间是否按键,目标函数总会在首次按键3秒后执行:
// compile on Linux with: // g++ test.cpp -o test.exe -pthread #include <iostream> #include <chrono> #include <thread> #include <functional> #include <sys/ioctl.h> // FIONREAD #include <sys/select.h> #include <termios.h> #include <stropts.h> std::chrono::time_point<std::chrono::steady_clock> tnow; std::chrono::time_point<std::chrono::steady_clock> tend; unsigned int interval_ms = 3000; bool do_stop = false; bool timer_started = false; int _kbhit() { static const int STDIN = 0; static bool initialized = false; if (! initialized) { // Use termios to turn off line buffering termios term; tcgetattr(STDIN, &term); term.c_lflag &= ~ICANON; tcsetattr(STDIN, TCSANOW, &term); setbuf(stdin, NULL); initialized = true; } int bytesWaiting; ioctl(STDIN, FIONREAD, &bytesWaiting); return bytesWaiting; } void update_timing() { std::cout << "updating timer" << std::endl; tnow = std::chrono::steady_clock::now(); tend = tnow + std::chrono::milliseconds(interval_ms); } void timer_start(std::function<void(void)> func) { std::cout << "starting timer" << std::endl; timer_started = true; tnow = std::chrono::steady_clock::now(); tend = tnow + std::chrono::milliseconds(interval_ms); std::thread([func]() { std::this_thread::sleep_until(tend); func(); }).detach(); } void the_function_to_delay(void) { std::cout << "The function (to delay) has ran!" << std::endl; do_stop = true; } int main() { std::cout << "Press any key to start timed function; press it again to update the timing" << std::endl; int kbhit_ret = 0; char kbchar; while( not(do_stop) ) { kbhit_ret = _kbhit(); if (kbhit_ret) { scanf( "%c", &kbchar ) ; std::cout << "kbhit " << kbhit_ret << " " << kbchar << std::endl; if (not(timer_started)) { timer_start( the_function_to_delay ); } else { update_timing(); } } std::cin.clear(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
核心问题:线程中的lambda表达式捕获的是tend的副本,后续update_timing()修改全局的tend变量时,线程内的副本不会同步更新,因此std::this_thread::sleep_until(tend)始终使用首次按键时的时间点,导致目标函数提前执行。
解决方案
通过原子变量存储目标执行时间+条件变量唤醒线程的方式,让线程能够感知到时间点的更新,从而实现延迟重置的效果。修改后的代码如下:
// compile on Linux with: // g++ test.cpp -o test.exe -pthread #include <iostream> #include <chrono> #include <thread> #include <functional> #include <atomic> #include <mutex> #include <condition_variable> #include <sys/ioctl.h> // FIONREAD #include <sys/select.h> #include <termios.h> #include <stropts.h> std::atomic<std::chrono::steady_clock::time_point> next_run_time; const unsigned int interval_ms = 3000; std::atomic<bool> do_stop = false; std::atomic<bool> timer_running = false; std::mutex timer_mutex; std::condition_variable timer_cv; int _kbhit() { static const int STDIN = 0; static bool initialized = false; if (! initialized) { termios term; tcgetattr(STDIN, &term); term.c_lflag &= ~ICANON; tcsetattr(STDIN, TCSANOW, &term); setbuf(stdin, NULL); initialized = true; } int bytesWaiting; ioctl(STDIN, FIONREAD, &bytesWaiting); return bytesWaiting; } void timer_thread(std::function<void()> func) { while (!do_stop) { auto now = std::chrono::steady_clock::now(); auto target = next_run_time.load(); // 检查是否到达执行时间 if (now >= target) { func(); break; } // 等待到目标时间,或被唤醒(重置定时器时) std::unique_lock<std::mutex> lock(timer_mutex); timer_cv.wait_until(lock, target, []{ return do_stop.load(); }); } timer_running = false; } void reset_timer() { auto now = std::chrono::steady_clock::now(); next_run_time.store(now + std::chrono::milliseconds(interval_ms)); timer_cv.notify_one(); // 唤醒线程重新检查时间点 } void start_timer(std::function<void()> func) { reset_timer(); timer_running = true; std::thread(timer_thread, func).detach(); } void the_function_to_delay(void) { std::cout << "The function (to delay) has ran!" << std::endl; do_stop = true; } int main() { std::cout << "Press any key to start timed function; press it again to update the timing" << std::endl; int kbhit_ret = 0; char kbchar; while (!do_stop) { kbhit_ret = _kbhit(); if (kbhit_ret) { scanf("%c", &kbchar); std::cout << "kbhit " << kbhit_ret << " " << kbchar << std::endl; if (!timer_running) { std::cout << "starting timer" << std::endl; start_timer(the_function_to_delay); } else { std::cout << "updating timer" << std::endl; reset_timer(); } } std::cin.clear(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
方案说明
- 原子变量存储时间点:用
std::atomic<std::chrono::steady_clock::time_point>存储下次执行的目标时间,确保主线程和定时器线程之间的时间点访问是线程安全的。 - 条件变量唤醒机制:每次重置定时器时,更新目标时间并通过
timer_cv.notify_one()唤醒定时器线程,让线程立即重新检查当前时间与目标时间的差值,避免无效等待。 - 循环检查逻辑:定时器线程内循环判断是否到达执行时间,确保每次重置后都能按照新的时间点计算等待时长。
内容的提问来源于stack exchange,提问作者sdbbs
相关产品推荐
相关产品推荐

