将信号定位到 C 中的特定线程
Targetting signal to specific thread in C
我们在我们的进程中使用 posix 间隔计时器(使用 timer_create() 创建),在计时器到期时生成 SIGALRM。生成的信号由进程中的特定线程异步处理 (sigwait),我们使用 sig_block 在所有其他线程中阻塞了该信号。 “Sig_block”在子线程生成之前在主线程中调用,因此子线程从父线程(即主线程)继承它。然而,这有一个警告,如果进程中包含的任何库在 dllmain 期间生成任何线程,信号将不会在该线程中被阻塞。此外,我们无法控制流程中包含的 DLL 的内部实现。你能建议如何处理这个问题吗?有没有其他方法可以将定时器到期信号定位到进程中的特定线程?
我勾选了选项'SIGEV_THREAD_ID'。但是文档指出它仅供线程库使用。
如果您不介意特定于 Linux,请使用 SIGEV_THREAD_ID
。此外,我建议使用实时信号(SIGRTMIN+0
到 SIGRTMAX-0
,包括在内),因为它们按照发送的顺序排队和传送。
SIGEV_THREAD_ID
被记录为仅供线程库使用的原因是 Linux 线程 ID 通常不会公开;该接口不能直接用于例如线程。您将需要实现自己的 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 pthreads 不做任何愚蠢的事情,比如在保持相同 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()
returns 指向超时的指针作为标识符,以便线程稍后可以解除它:
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()
超时结构(除非你取消注释接近末尾的行),如果找不到超时,它returns -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 的天真行为——太天真了我会认为它有缺陷——您有一些可移植的选项。
在 exec
.
之前,您可以在 SIGALRM 已被阻止的情况下调用您的程序
您的计时器可以改为指定 SIGEV_THREAD,然后该线程可以处理超时或通知您的专用线程该工作了。
您可以像 Glärbo .
一样在同步休眠线程中实现在没有信号的情况下保持自己的时间
我们在我们的进程中使用 posix 间隔计时器(使用 timer_create() 创建),在计时器到期时生成 SIGALRM。生成的信号由进程中的特定线程异步处理 (sigwait),我们使用 sig_block 在所有其他线程中阻塞了该信号。 “Sig_block”在子线程生成之前在主线程中调用,因此子线程从父线程(即主线程)继承它。然而,这有一个警告,如果进程中包含的任何库在 dllmain 期间生成任何线程,信号将不会在该线程中被阻塞。此外,我们无法控制流程中包含的 DLL 的内部实现。你能建议如何处理这个问题吗?有没有其他方法可以将定时器到期信号定位到进程中的特定线程?
我勾选了选项'SIGEV_THREAD_ID'。但是文档指出它仅供线程库使用。
如果您不介意特定于 Linux,请使用 SIGEV_THREAD_ID
。此外,我建议使用实时信号(SIGRTMIN+0
到 SIGRTMAX-0
,包括在内),因为它们按照发送的顺序排队和传送。
SIGEV_THREAD_ID
被记录为仅供线程库使用的原因是 Linux 线程 ID 通常不会公开;该接口不能直接用于例如线程。您将需要实现自己的 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 pthreads 不做任何愚蠢的事情,比如在保持相同 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()
returns 指向超时的指针作为标识符,以便线程稍后可以解除它:
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()
超时结构(除非你取消注释接近末尾的行),如果找不到超时,它returns -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 的天真行为——太天真了我会认为它有缺陷——您有一些可移植的选项。
在 exec
.
您的计时器可以改为指定 SIGEV_THREAD,然后该线程可以处理超时或通知您的专用线程该工作了。
您可以像 Glärbo