Написал для своего небольшого проекта обёртку над таймерами Posix.
class VirtualTimer {
private:
pthread_mutex_t _mutex;
timer_t _posixTimer;
uint32_t _period;
TimerCallback _callback;
void *_callbackArg;
volatile bool _armed;
static void posixTimerCallback(union sigval val);
public:
VirtualTimer(uint32_t period = 0, TimerCallback callback = NULL, void *arg = NULL);
~VirtualTimer();
void setCallback(TimerCallback callback, void *arg);
bool setPeriod(uint32_t period);
uint32_t getPeriod() {
return _period;
}
void start();
void stop();
bool isArmed() {
return _armed;
}
};
VirtualTimer::VirtualTimer(uint32_t period, TimerCallback callback, void *arg) {
_period = period;
_callback = callback;
_callbackArg = arg;
_armed = false;
pthread_mutex_init(&(_mutex), NULL);
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = posixTimerCallback;
sev.sigev_value.sival_ptr = this;
int r = timer_create(CLOCK_MONOTONIC, &sev, &(_posixTimer));
assert(r == 0);
if (_period > 0) {
start();
}
}
VirtualTimer::~VirtualTimer() {
timer_delete(_posixTimer);
pthread_mutex_lock(&(_mutex));
pthread_mutex_destroy(&(_mutex));
}
void VirtualTimer::posixTimerCallback(union sigval val) {
VirtualTimer *timer = (VirtualTimer*)val.sival_ptr;
pthread_mutex_lock(&(timer->_mutex));
if ((timer->_callback != NULL) && (!timer->_callback(timer->_callbackArg))) {
timer->stop();
}
pthread_mutex_unlock(&(timer->_mutex));
}
void VirtualTimer::setCallback(TimerCallback callback, void *arg) {
_callback = callback;
_callbackArg = arg;
}
bool VirtualTimer::setPeriod(uint32_t period) {
_period = period;
if (_armed) {
start();
}
return true;
}
void VirtualTimer::start() {
time_t sec = _period / 1000000;
long nsec = (_period % 1000000) * 1000;
struct itimerspec newValue = {
.it_interval = {
.tv_sec = sec,
.tv_nsec = nsec
},
.it_value = {
.tv_sec = sec,
.tv_nsec = nsec
}
};
int r = timer_settime(_posixTimer, 0, &newValue, NULL);
assert(r == 0);
_armed = true;
}
void VirtualTimer::stop() {
struct itimerspec newValue = {};
int r = timer_settime(_posixTimer, 0, &newValue, NULL);
assert(r == 0);
_armed = false;
}
Имеется проблема. Допустим, таймер срабатывает и вызывается callback. Однако в этот же момент поток, создавший таймер, решает, что хватит дожидаться его срабатывания и уничтожает объект. В итоге callback (с самого начала или на своей середине) будет работать с уничтоженным объектом и это может привести к печальным последствиям.
Сейчас это частично решается с помощью mutex. Если callback успеет захватить mutex, то деструктор будет терпеливо ждать, пока callback отработает (новый callback уже точно не вызовется, потому что таймер дизармится перед захватом mutex) и только потом уничтожит таймер. Однако если нет? Если деструктор успеет захватить mutex, затем будет вызван callback (callback уже может быть вызван, но таймерный поток ожидает своего кванта времени в очереди планировщика ОС, пока работает деструктор), который будет ждать mutex, который потом уничтожат (что уже само по себе будет плохо, и либо будет зависание таймерного потока, либо обращение к освобождённой области памяти)? Будет плохо.
Как эту ситуацию разрулить?