Jason Jason - 1 month ago 6
C++ Question

Multithreaded timer class

I've created a timer class that performs a user supplied action (function that takes no arguments has no return type) at a user supplied interval. This action should be performed in its own thread--i.e. when the timer is created, a new thread is created, and that thread consists of a loop that uses

sigwait
to wait for the signal to come in before performing the callback. The signal I want to use will be anywhere from
SIGRTMIN
to
SIGRTMAX
. I want to be able to create multiple timer objects which means multiple threads and multiple signals (one thread and signal per timer). Using this post, the
timer_create
man page, and
pthread_sigmask
man page as references, this is what I have:

//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


and

//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();
}
}


For testing I am using this:

//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();
}


When running, it will crash pretty quickly with the error "Real-time signal 1". If I run it in gdb, I get

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.


which is interesting because 35 is what SIGRTMIN+1 is equal to. So maybe I'm not routing the signals correctly? If I only create once instance of the timer in the driver.cpp file, things appear to work ok. Any thoughts are appreciated.

I'm also curious if this is even the right approach to what I'm trying to do. In some brief tests I did, using the system signals seems way more stable than using sleep and usleep to burn up unused loop time.

Answer

My guess is that the signal used to wake second thread (CTimer t2) is not blocked by the first thread (CTimer t1). Signal mask in a thread is inherited from a parent thread, so when you start first thread it only has SIGRTMIN signal blocked, but SIGRTMIN+1 can still be delivered to it. Standard reaction to real-time signals is to terminate process, this is what happens. You can test this theory by blocking all real-time signals in all threads started by CTimer class.

I'm not sure why you think that sleep/usleep is less reliable than your own solution, using the right patterns with usleep (basically expecting that it can return sooner and waiting in a loop) always worked OK for me.