簡體   English   中英

將信號定位到 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+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 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()的調用會返回一個指向超時的指針作為標識符,以便線程稍后可以解除它:

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

注意上面的 function 並沒有free()超時結構(除非你取消注釋接近末尾的行),如果找不到超時它返回-1 ,如果成功則刪除超時時的elapsed字段.

管理超時的線程 function 相當簡單:


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建議的那樣,您可以在同步休眠的線程中實現在沒有信號的情況下保持自己的時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM