You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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));
  }
}

方案说明

  1. 原子变量存储时间点:用std::atomic<std::chrono::steady_clock::time_point>存储下次执行的目标时间,确保主线程和定时器线程之间的时间点访问是线程安全的。
  2. 条件变量唤醒机制:每次重置定时器时,更新目标时间并通过timer_cv.notify_one()唤醒定时器线程,让线程立即重新检查当前时间与目标时间的差值,避免无效等待。
  3. 循环检查逻辑:定时器线程内循环判断是否到达执行时间,确保每次重置后都能按照新的时间点计算等待时长。

内容的提问来源于stack exchange,提问作者sdbbs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 02:07:04