If you do not mind being Linux-specific, use SIGEV_THREAD_ID. Also, I recommend using a realtime signal (SIGRTMIN+0 through SIGRTMAX-0, inclusive), since these are queued and delivered in the order they were sent.
The reason SIGEV_THREAD_ID is documented as intended for use only by threading libraries is that Linux thread IDs are not normally exposed; this interface is not directly usable with e.g. pthreads. You will need to implement your own gettid():
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
static inline pid_t gettid(void) { return syscall(SYS_gettid); }
That will rely on Linux pthreads not doing anything silly, like switching thread-ids while keeping the same pthread_t ID.
Personally, I suggest a different approach, using a helper thread to maintain the timeouts.
Have a thread maintain a sorted array or a binary heap of timeout timestamps, associated with the target thread ID (pthread_t). The thread will wait in pthread_cond_timedwait() until next timeout expires, or it is signaled, indicating that the timeouts have changed (cancelled or new ones added). When one or more timeouts expire, the thread uses pthread_sigqueue() to send the appropriate signal to the target thread, with the timeout identifier as a payload.
Perhaps a rough simplified sketch helps understand. For simplicity, let's say the pending timeouts form a singly linked list:
struct timeout {
struct timeout *next;
struct timespec when; /* Absolute CLOCK_REALTIME time */
double repeat; /* Refire time in seconds, 0 if single-shot */
pthread_id thread;
int elapsed;
};
pthread_mutex_t timeout_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t timeout_wait = PTHREAD_COND_INITIALIZER;
struct timeout *timeout_pending = NULL;
int timeout_quit = 0;
static inline int timespec_cmp(const struct timespec t1, const struct timespec t2)
{
return (t1.tv_sec < t2.tv_sec) ? -1 :
(t1.tv_sec > t2.tv_sec) ? +1 :
(t1.tv_nsec < t2.tv_nsec) ? -1 :
(t1.tv_nsec > t2.tv_nsec) ? +1 : 0;
}
static inline void timespec_add(struct timespec *const ts, const double seconds)
{
if (seconds > 0.0) {
ts->tv_sec += (long)seconds;
ts->tv_nsec += (long)(1000000000.0*(double)(seconds - (long)seconds));
if (ts->tv_nsec < 0)
ts->tv_nsec = 0;
if (ts->tv_nsec >= 1000000000) {
ts->tv_sec += ts->tv_nsec / 1000000000;
ts->tv_nsec = ts->tv_nsec % 1000000000;
}
}
}
struct timeout *timeout_arm(double seconds, double repeat)
{
struct timeout *mark;
mark = malloc(sizeof (timeout));
if (!mark) {
errno = ENOMEM;
return NULL;
}
mark->thread = pthread_self();
mark->elapsed = 0;
clock_gettime(CLOCK_REALTIME, &(mark->when));
timespec_add(&(mark->when), seconds);
mark->repeat = repeat;
pthread_mutex_lock(&timeout_lock);
mark->next = timeout_pending;
timeout_pending = mark;
pthread_cond_signal(&timeout_wait);
pthread_mutex_unlock(&timeout_lock);
return mark;
A call to timeout_arm() returns a pointer to the timeout as an identifier, so that the thread can disarm it later:
int timeout_disarm(struct timeout *mark)
{
int result = -1;
pthread_mutex_lock(&timeout_lock);
if (timeout_pending == mark) {
timeout_pending = mark->next;
mark->next = NULL;
result = mark->elapsed;
} else {
struct timeout *list = timeout_pending;
for (; list->next != NULL; list = list->next) {
if (list->next == mark) {
list->next = mark->next;
mark->next = NULL;
result = mark->elapsed;
break;
}
}
}
/* if (result != -1) free(mark); */
pthread_mutex_unlock(&timeout_lock);
return result;
}
Note that the above function does not free() the timeout structure (unless you uncomment the line near the end), and it returns -1 if the timeout cannot be found, and the elapsed field at the time when the timeout was removed if successful.
The thread function managing the timeouts is rather simple:
void *timeout_worker(void *unused)
{
struct timespec when, now;
struct timeout *list;
pthread_mutex_lock(&timeout_lock);
while (!timeout_quit) {
clock_gettime(CLOCK_REALTIME, &now);
/* Let's limit sleeps to, say, one minute in length. */
when = now;
when.tv_sec += 60;
/* Act upon all elapsed timeouts. */
for (list = timeout_pending; list != NULL; list = list->next) {
if (timespec_cmp(now, list->when) >= 0) {
if (!list->elapsed || list->repeat > 0) {
const union sigval value = { .sival_ptr = list };
list->elapsed++;
pthread_sigqueue(list->thread, TIMEOUT_SIGNAL, value);
timespec_add(&(list->when), list->repeat);
}
} else
if (timespec_cmp(when, list->when) < 0) {
when = list->when;
}
}
pthread_cond_timedwait(&timeout_wait, &timeout_lock, &when);
}
/* TODO: Clean up timeouts_pending list. */
return NULL;
}
Note that I haven't checked the above for typos, so there might be some. All code above is licensed under CC0-1.0: do whatever you want, just don't blame me for any errors.
SIGEV_THREAD_IDif you don't mind being linux specific. - Shawn