c中特定线程的目标信号



我们在计时器到期时生成SIGALRM的进程中使用posix间隔计时器(使用timer_create()创建)。生成的信号由进程中的特定线程异步处理(sigwait),我们使用sig_block阻塞了所有其他线程中的信号。' Sig_block '在子线程生成之前在主线程中被调用,因此子线程从父线程(即main)继承它。但是,这需要注意,如果进程中包含的任何库在dllmain期间生成任何线程,则信号不会在该线程中被阻塞。此外,我们无法控制包含在流程中的dll的内部实现。你能建议如何处理这个问题吗?是否有其他方法将定时器过期信号定位到进程中的特定线程?

我检查了选项'SIGEV_THREAD_ID'。然而,文档声明它仅供线程库使用。

如果您不介意使用特定于linux的SIGEV_THREAD_ID。此外,我建议使用实时信号(SIGRTMIN+0SIGRTMAX-0,包括在内),因为这些信号是按发送的顺序排队和交付的。

SIGEV_THREAD_ID被记录为仅供线程库使用的原因是Linux线程id通常不公开;这个接口不能直接用于例如pthreads。您需要实现自己的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); }

这将依赖于Linux的pthread不做任何愚蠢的事情,比如在保持相同的pthread_t ID的同时切换线程ID。

我个人建议使用一种不同的方法,使用一个帮助线程来维护超时。

让线程维护与目标线程ID (pthread_t)相关联的超时时间戳的排序数组或二进制堆。线程将在pthread_cond_timedwait()中等待,直到下一个超时过期,或者发出信号,表明超时已更改(取消或添加新超时)。当一个或多个超时过期时,线程使用pthread_sigqueue()向目标线程发送适当的信号,并将超时标识符作为有效负载。

也许一个粗略的简化草图有助于理解。为简单起见,假设挂起的超时形成一个单链表:

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;

调用timeout_arm()返回一个指向超时的指针作为标识符,以便线程可以稍后解除它:

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;
}

请注意,上面的函数没有free()超时结构(除非您取消末尾附近的行注释),如果找不到超时,它返回-1,如果成功,则返回超时移除时的elapsed字段。

管理超时的线程函数相当简单:


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;    
}

注意,我没有检查上面的错字,所以可能会有一些。上面所有的代码都是在CC0-1.0下授权的:你想做什么就做什么,只是不要因为任何错误而责怪我。

不幸的是,您所希望的将计时器信号定向到特定线程的行为是不可移植的。

工作围绕您的DLL的naïve行为-所以naïve我认为它有bug -你有几个便携式的选项。

在调用exec之前,您可以调用已经阻塞了SIGALRM的程序。

你的计时器可以指定SIGEV_THREAD,然后该线程可以处理超时或通知你的专用线程该工作了。

您可以在同步睡眠线程中实现自己的时间保持,而不需要信号,如Glärbo所建议的。

最新更新