多线程计时器类

时间:2016-10-24 19:17:13

标签: c++ pthreads signals ubuntu-14.04

我创建了一个计时器类,它以用户提供的间隔执行用户提供的操作(不带参数的函数没有返回类型)。这个动作应该在自己的线程中进行 - 即。创建计时器时,会创建一个新线程,该线程由一个循环组成,该循环在执行回调之前使用sigwait等待信号进入。我想要使​​用的信号将是从SIGRTMINSIGRTMAX的任何地方。我希望能够创建多个计时器对象,这意味着多个线程和多个信号(每个计时器一个线程和信号)。使用this posttimer_create man pagepthread_sigmask man page作为参考,这就是我所拥有的:

//timer.h
#ifndef TIMERS_H
#define TIMERS_H
#include <signal.h>
#include <time.h>
#include <inttypes.h>
#include <stdio.h>
#include <pthread.h>

class CTimer
{
public:
    CTimer(uint64_t period_ms, void(*callback)(void), int sig );
private:
    typedef void (*Callback)(void);
    Callback m_pCallback;
    timer_t timerID;
    struct sigevent sev;
    struct itimerspec its;
    struct sigaction sa;
    uint8_t timerNum;

    pthread_t thread_id;
    sigset_t set;
    int signal_ID;
    void* loop();

    friend void* run_loop(void* arg);
};
#endif // TIMERS_H

//timer.cpp
#include "timers.h"

void* run_loop(void* arg)
{
    return static_cast<CTimer*>(arg)->loop();
}

CTimer::CTimer(uint64_t period_ms, void(*callback)(void), int sig):
               m_pCallback(callback), signal_ID(sig)
{
    //create mask to send appropriate signal to thread
    int s;
    sigemptyset(&set);
    s = sigaddset(&set, signal_ID);
    if (s != 0)
    {
        printf("error on sigaddset\n");
    }
    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (s != 0)
    {
        printf("error on pthread_sigmask\n");
    }
    //create new thread that will run the signal handler
    s = pthread_create(&thread_id, NULL, run_loop, this);
    if (s != 0)
    {
        printf("error on pthread_create\n");
    }

    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = signal_ID;
    sev.sigev_value.sival_ptr = &timerID;
    if (timer_create(CLOCK_REALTIME, &sev, &timerID) == -1)
    {
        printf("error on timer create\n");
    }
    its.it_value.tv_sec = period_ms / 1000;
    its.it_value.tv_nsec = period_ms % 1000;
    its.it_interval.tv_sec = its.it_value.tv_sec;
    its.it_interval.tv_nsec = its.it_value.tv_nsec;

    if (timer_settime(timerID, 0, &its, NULL) == -1)
    {
        printf("error on timer settime\n");
    }
}

void* CTimer::loop()
{
    int s = 0;
    while (1)
    {
        s = sigwait(&set, &signal_ID);
        m_pCallback();
    }
}

为了测试我正在使用它:

//driver.cpp
#include <stdio.h>
#include <unistd.h>
#include "sys/time.h"
#include "timers.h"

uint64_t get_time_usec()
{
    static struct timeval _time_stamp;
    gettimeofday(&_time_stamp, NULL);
    return _time_stamp.tv_sec*1000000 + _time_stamp.tv_usec;
}

void callbacktest1()
{
    printf("tick1 %" PRIu64 " \n", get_time_usec());
}

void callbacktest2()
{
    printf("tick2 %" PRIu64 " \n", get_time_usec());
}

int main(int argv, char *argc[])
{
    CTimer t1(1000, callbacktest1, SIGRTMIN);
    CTimer t2(2000, callbacktest2, SIGRTMIN+1);

    pause();
}

运行时,它会很快崩溃并出现错误&#34;实时信号1&#34;。如果我在gdb中运行它,我会得到

Starting program: /home/overlord/MySource/Timer/driver 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff75ee700 (LWP 21455)]
[New Thread 0x7ffff6ded700 (LWP 21456)]
tick1 1477336403700925 
tick1 1477336404700920 

Program received signal SIG35, Real-time event 35.
[Switching to Thread 0x7ffff75ee700 (LWP 21455)]
0x00007ffff7bcc0c1 in do_sigwait (sig=0x7fffffffdbc8, set=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/../../../../../sysdeps/unix/sysv/linux/sigwait.c:60
60      ../nptl/sysdeps/unix/sysv/linux/../../../../../sysdeps/unix/sysv/linux/sigwait.c: No such file or directory.

这很有趣,因为35是SIGRTMIN + 1等于的。那么也许我没有正确路由信号?如果我只在driver.cpp文件中创建一次计时器实例,那么事情似乎正常。任何想法都表示赞赏。

我也很好奇这是否是我正在尝试做的正确方法。在我做的一些简短的测试中,使用系统信号看起来比使用睡眠和睡眠更加稳定,以消耗未使用的循环时间。

2 个答案:

答案 0 :(得分:1)

我的猜测是用于唤醒第二个线程(CTimer t2)的信号没有被第一个线程(CTimer t1)阻止。线程中的信号掩码是从父线程继承的,因此当您启动第一个线程时,它只会阻塞SIGRTMIN信号,但SIGRTMIN+1仍然可以传递给它。对实时信号的标准反应是终止过程,这就是发生的事情。您可以通过阻止CTimer类启动的所有线程中的所有实时信号来测试此理论。

我不确定为什么你认为sleep / usleep不如你自己的解决方案可靠,使用正确的模式与usleep(基本上期望它可以更快地返回并在循环中等待)总是对我有效。< / p>

答案 1 :(得分:1)

  

我不确定为什么你认为睡眠/睡眠不太可靠   你自己的解决方案,使用正确的模式与usleep(基本上   期待它能够更快地返回并且在循环中等待)   为我工作好。

我使用以下代码进行了基本测试:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <inttypes.h>
#include <math.h>

#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN

#define SQ(x) ((x)*(x))

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                       } while (0)

uint64_t start_time_us = 0;
double error_ms = 0;
int64_t count = 0;
int64_t last_time_us = 0;
uint64_t period_ns;

uint64_t get_time_usec()
{
    static struct timeval _time_stamp;
    gettimeofday(&_time_stamp, NULL);
    return _time_stamp.tv_sec*1000000 + _time_stamp.tv_usec;
}

static void
handler(int sig, siginfo_t *si, void *uc)
{
   uint64_t timestamp_us = get_time_usec();
   double dt_ms = (double)(timestamp_us - last_time_us)/1000;
   double elapsed_ms = (double)(timestamp_us - start_time_us)/1000;
   error_ms += SQ((dt_ms - (double)period_ns/1000000.0));
   count++;
   last_time_us = timestamp_us;
}

namespace hidi
{
  void pause(const double& tSeconds)
  {
    unsigned int decimal = static_cast<unsigned int>(floor(tSeconds));
    double fraction = tSeconds-static_cast<double>(decimal);
    if (decimal > 0)
      sleep(decimal);
    usleep(static_cast<unsigned long>(floor(fraction*1000000.0)));
    return;
  }
}

int
main(int argc, char *argv[])
{
   timer_t timerid;
   struct sigevent sev;
   struct itimerspec its;

   //sigset_t mask;
   struct sigaction sa;

   if (argc != 3) {
       fprintf(stderr, "Usage: %s <test length-secs> <period-millisec>\n",
               argv[0]);
       exit(EXIT_FAILURE);
   }
   uint64_t period_ms = atoll(argv[2]);
   period_ns = period_ms * 1000000;

   /// FIRST TEST LOOP RATE STABILITY USING THE TIMER
   // THE TIMER WILL USE SIGRTMIN (DEFINED ABOVE) AND WILL MEASURE
   // STATISTICS ON LOOP STABILITY

   /* Establish handler for timer signal */

   printf("Establishing handler for signal %d\n", SIG);
   sa.sa_flags = SA_SIGINFO;
   sa.sa_sigaction = handler;
   sigemptyset(&sa.sa_mask);
   if (sigaction(SIG, &sa, NULL) == -1)
       errExit("sigaction");

   /* Create the timer */

   sev.sigev_notify = SIGEV_SIGNAL;
   sev.sigev_signo = SIG;
   sev.sigev_value.sival_ptr = &timerid;
   if (timer_create(CLOCKID, &sev, &timerid) == -1)
       errExit("timer_create");

   printf("timer ID is 0x%lx\n", (long) timerid);

   /* Start the timer */

   printf("Timing period is %zu ms\n", period_ms);
   its.it_value.tv_sec = period_ns / 1000000000;
   its.it_value.tv_nsec = period_ns % 1000000000;
   its.it_interval.tv_sec = its.it_value.tv_sec;
   its.it_interval.tv_nsec = its.it_value.tv_nsec;

   if (timer_settime(timerid, 0, &its, NULL) == -1)
        errExit("timer_settime");
   start_time_us = last_time_us = get_time_usec();

   printf("Sleeping for %d seconds\n", atoi(argv[1]));


   while ((get_time_usec()-start_time_us)/1000000 < atoi(argv[1]))
   {
    sleep(1); //this just prevents the while loop from spinning out of control
              // the sleep function is interrupted with the signal callback is
              // executed. All the magic happens in the callback.
   }
   printf("ave error: %8.6f ms %zu samples\n",sqrt((double)error_ms/(double)count), count);

   timer_delete(timerid); // disarm / delete timer


   /// START TEST USING SLEEP / USLEEP
   start_time_us = last_time_us = get_time_usec();
   error_ms = count = 0;

   while ((get_time_usec()-start_time_us)/1000000 < atoi(argv[1]))
   {
     uint64_t timestamp_us = get_time_usec();
     double dt_ms = (double)(timestamp_us - last_time_us)/1000;
     double elapsed_ms = (double)(timestamp_us - start_time_us)/1000;
     error_ms += SQ((dt_ms - (double)period_ns/1000000.0));
     //printf("et=%8.6f ms, dt=%8.6f ms ave error %f\n", elapsed_ms, dt_ms, error_ms/count);
     count++;
     last_time_us = timestamp_us;
     uint64_t consumed_time_us = get_time_usec()-timestamp_us;
     uint64_t remaining_looptime_us = (period_ns/1000) - consumed_time_us;
     hidi::pause((double)remaining_looptime_us/1000000.0);
    }

    printf("ave error: %8.6f ms %zu samples\n",sqrt((double)error_ms/(double)count), count);

   exit(EXIT_SUCCESS);
}

测试时间从10毫秒到2秒,使用计时器似乎更稳定。似乎使用sleep / usleep方法,误差与周期成正比,即10 ms周期为2-3 ms,而1000 ms周期为300 - 400 ms。使用计时器,错误在不同的时期内非常稳定。