【问题标题】:Calling functions at timed intervals using threads使用线程以定时间隔调用函数
【发布时间】:2014-10-27 17:57:59
【问题描述】:

我正在构建一个模拟器来测试一个非常简单的机器人的学生代码。我需要定期在单独的线程上运行两个功能(更新机器人传感器和机器人位置)。我当前的实现是处理器效率极低的,因为它有一个线程专门用于简单地递增数字以跟踪代码中的位置。我最近的理论是,我可以使用 sleep 来给出传感器更新值和机器人位置之间的时间延迟。我的第一个问题是:这有效吗?第二:有什么方法可以做一件简单的事情,但测量时钟周期而不是秒?

【问题讨论】:

  • 你能更详细地解释你希望做什么吗?为什么传感器和位置有不同的线程?这两个线程是否以任何方式相互依赖?除非测试代码做了一些非常繁重的工作,否则我怀疑您是否需要认真关注优化测试代码的效率。

标签: c++ multithreading pthreads scheduled-tasks


【解决方案1】:

通过等待类似互斥的对象使线程进入睡眠状态通常是有效的。一种常见的模式是在互斥体上等待超时。当达到超时时,时间间隔就结束了。当互斥体被释放时,就是线程终止的信号。

伪代码:

void threadMethod() {
  for(;;) {
    bool signalled = this->mutex.wait(1000);
    if(signalled) {
      break; // Signalled, owners wants us to terminate
    }

    // Timeout, meaning our wait time is up
    doPeriodicAction();
  }
}

void start() {
  this->mutex.enter();
  this->thread.start(threadMethod);
}

void stop() {
  this->mutex.leave();
  this->thread.join();
}

在 Windows 系统上,超时通常以毫秒为单位指定,并且精确到大约 16 毫秒(timeBeginPeriod() 可能能够改善这一点)。我不知道 CPU 周期触发的同步原语。有称为“临界区”的轻量级互斥体,它们在委托给 OS 线程调度程序之前将 CPU 旋转几千个周期。在这段时间内,它们是相当准确的。

在 Linux 系统上,准确度可能会更高一些(高频定时器或无滴答内核),除了互斥体之外,还有类似于 Windows 的临界区的“futexes”(快速互斥体)。


我不确定我是否掌握了您想要实现的目标,但如果您想测试学生代码,您可能需要使用虚拟时钟并自己控制时间的流逝。例如通过调用学生必须提供的processInputs()decideMovements() 方法。每次调用后,有 1 个时隙结束。

【讨论】:

    【解决方案2】:

    此 C++11 代码使用 std::chrono::high_resolution_clock 测量亚秒级计时,并使用 std::thread 运行三个线程。 std::this_thread::sleep_for()函数用于休眠指定时间。

    #include <iostream>
    #include <thread>
    #include <vector>
    #include <chrono>
    
    void seconds()
    {
        using namespace std::chrono;
    
        high_resolution_clock::time_point t1, t2; 
        for (unsigned i=0; i<10; ++i) {
            std::cout << i << "\n";
            t1 = high_resolution_clock::now();
            std::this_thread::sleep_for(std::chrono::seconds(1)); 
            t2 = high_resolution_clock::now();
            duration<double> elapsed = duration_cast<duration<double> >(t2-t1);
            std::cout << "\t( " << elapsed.count() << " seconds )\n";
        }
    }
    int main()
    {
        std::vector<std::thread> t;
    
        t.push_back(std::thread{[](){ 
            std::this_thread::sleep_for(std::chrono::seconds(3)); 
            std::cout << "awoke after 3\n"; }});
        t.push_back(std::thread{[](){ 
            std::this_thread::sleep_for(std::chrono::seconds(7)); 
            std::cout << "awoke after 7\n"; }});
        t.push_back(std::thread{seconds});
    
        for (auto &thr : t)
            thr.join();
    }
    

    很难知道这是否满足您的需求,因为问题中缺少很多细节。 Linux下编译:

    g++ -Wall -Wextra -pedantic -std=c++11 timers.cpp -o timers -lpthread
    

    我的机器上的输出:

    0
        ( 1.00014 seconds)
    1
        ( 1.00014 seconds)
    2
    awoke after 3
        ( 1.00009 seconds)
    3
        ( 1.00015 seconds)
    4
        ( 1.00011 seconds)
    5
        ( 1.00013 seconds)
    6
    awoke after 7
        ( 1.0001 seconds)
    7
        ( 1.00015 seconds)
    8
        ( 1.00014 seconds)
    9
        ( 1.00013 seconds)
    

    其他可能感兴趣的 C++11 标准功能包括 timed_mutexpromise/future

    【讨论】:

      【解决方案3】:

      是的,你的理论是正确的。您可以使用 sleep 在线程执行函数之间设置一些延迟。效率取决于您可以选择多宽的延迟以获得所需的结果。 你必须解释你的实施细节。例如,我们不知道两个线程是否相互依赖(在这种情况下,您必须注意同步,这会破坏一些周期)。

      【讨论】:

      • 你可以使用sleep(),是的。但在大多数情况下,这可能是一个非常糟糕的主意(你最终可能会忙着等待)。更合适的方法是使用定时信号量/条件变量。
      【解决方案4】:

      这是一种方法。我正在使用 C++11、线程、原子和高精度时钟。调度程序将回调一个函数,该函数需要 dt 秒,这是自上次调用以来经过的时间。可以通过调用 if 回调函数返回 false 的 stop() 方法来停止循环。

      调度程序代码

      #include <thread>
      #include <chrono>
      #include <functional>
      #include <atomic>
      #include <system_error>
      
      class ScheduledExecutor {
      public:
          ScheduledExecutor()
          {}
          ScheduledExecutor(const std::function<bool(double)>& callback, double period)
          {
              initialize(callback, period);
          }
          void initialize(const std::function<bool(double)>& callback, double period)
          {
              callback_ = callback;
              period_ = period;
              keep_running_ = false;
          }
      
          void start()
          {
              keep_running_ = true;
              sleep_time_sum_ = 0;
              period_count_ = 0;
              th_ = std::thread(&ScheduledExecutor::executorLoop, this);
          }
      
          void stop()
          {
              keep_running_ = false;
              try {
                  th_.join();
              }
              catch(const std::system_error& /* e */)
              { }
          }
      
          double getSleepTimeAvg()
          {
              //TODO: make this function thread safe by using atomic types
              //right now this is not implemented for performance and that
              //return of this function is purely informational/debugging purposes
              return sleep_time_sum_ / period_count_;
          }
      
          unsigned long getPeriodCount()
          {
              return period_count_;
          }
      
      private:
          typedef std::chrono::high_resolution_clock clock;
          template <typename T>
          using duration = std::chrono::duration<T>;
      
          void executorLoop()
          {
              clock::time_point call_end = clock::now();
              while (keep_running_) {
                  clock::time_point call_start = clock::now();
                  duration<double> since_last_call = call_start - call_end;
      
                  if (period_count_ > 0 && !callback_(since_last_call.count()))
                      break;
      
                  call_end = clock::now();
      
                  duration<double> call_duration = call_end - call_start;
                  double sleep_for = period_ - call_duration.count();
                  sleep_time_sum_ += sleep_for;
                  ++period_count_;
                  if (sleep_for > MinSleepTime)
                      std::this_thread::sleep_for(std::chrono::duration<double>(sleep_for));
              }
          }
      
      private:
          double period_;
          std::thread th_;
          std::function<bool(double)> callback_;
          std::atomic_bool keep_running_;
      
          static constexpr double MinSleepTime = 1E-9;
      
          double sleep_time_sum_;
          unsigned long period_count_;
      };
      

      使用示例

      bool worldUpdator(World& w, double dt)
      {
          w.update(dt);
          return true;
      }
      
      void main() {
          //create world for your simulator
          World w(...);
      
          //start scheduler loop for every 2ms calls
          ScheduledExecutor exec;
          exec.initialize(
              std::bind(worldUpdator, std::ref(w), std::placeholders::_1), 
                  2E-3);
          exec.start();
      
          //main thread just checks on the results every now and then
          while (true) {
              if (exec.getPeriodCount() % 10000 == 0) {
                  std::cout << exec.getSleepTimeAvg() << std::endl;
              }
          }
      }
      

      还有otherrelated关于SO的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-09
        • 2012-04-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-26
        相关资源
        最近更新 更多