首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在我想要的任何时候暂停一个线程?

如何在我想要的任何时候暂停一个线程?
EN

Stack Overflow用户
提问于 2012-02-22 22:49:49
回答 4查看 11.7K关注 0票数 6

最近,我开始将ucos-ii移植到Ubuntu PC上。

正如我们所知道的,通过简单地在pthread的回调函数的"while“循环中添加一个标志来执行暂停和恢复(就像下面的解决方案一样),不可能在ucos-ii中模拟”进程“。因为ucos-ii中的“进程”可以在任何时候暂停或恢复!

How to sleep or pause a PThread in c on Linux

我在下面的网站上找到了一个解决方案,但它不能构建,因为它是过时的。它使用Linux中的进程来模拟ucos-ii中的任务(行为类似于我们的Linux中的进程)。

http://www2.hs-esslingen.de/~zimmerma/software/index_uk.html

如果pthread可以像进程一样,可以随时暂停和恢复,请告诉我一些相关的函数,我可以自己弄明白。如果不能,我认为我应该专注于较旧的解决方案。非常感谢。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-02-23 14:54:20

如果使用条件变量在特定点停止是不够的,那么您不能使用pthread来做到这一点。pthread接口不包括挂起/恢复功能。

例如,请参见答案E.4 here

POSIX标准没有提供线程A可以在没有来自B的合作的情况下挂起另一个线程B的执行的机制。实现挂起/重启机制的唯一方法是让B周期性地检查挂起请求的某个全局变量,然后在条件变量上挂起自己,另一个线程可以稍后用信号通知该条件变量以重启B。

常见问题解答接着描述了两种非标准的方法,一种是在Solaris中,另一种是在LinuxThreads中(它现在已经过时了;不要将它与Linux上的当前线程混淆);这两种方法都不适用于您的情况。

票数 4
EN

Stack Overflow用户

发布于 2014-08-17 00:55:25

Modula 3垃圾收集器需要在任意时间挂起pthread,而不仅仅是在它们等待条件变量或互斥时。它通过注册一个(Unix)信号处理程序来挂起线程,然后使用pthread_kill向目标线程发送信号。我认为它可以工作(它对其他人来说是可靠的,但我现在正在调试它的一个问题……)不过,这有点笨拙...

在谷歌上搜索ThreadPThread.m3,看看例程"StopWorld“和"StartWorld”。处理程序本身在ThreadPThreadC.c中。

票数 5
EN

Stack Overflow用户

发布于 2021-06-24 23:59:33

在Linux上,您可能可以设置自定义信号处理程序(例如,使用信号()),它将包含等待另一个信号(例如,使用sigsuspend())。然后使用pthread_kill()或tgkill()发送信号。为此,使用所谓的“实时信号”很重要,因为像SIGUSR1和SIGUSR2这样的正常信号不会排队,这意味着它们可能在高负载条件下丢失。您多次发送一个信号,但它只接收到一次,因为以前在信号处理程序运行时,同类的新信号会被忽略。因此,如果你有并发的线程执行暂停/恢复,你可能会松散恢复事件并导致死锁。另一方面,未对挂起的实时信号(如SIGRTMIN+1和SIGRTMIN+2)进行重复数据删除,因此队列中可能同时存在多个相同的rt信号。

免责声明:我还没有尝试过。但从理论上讲,它应该是可行的。

另请参阅man 7信号安全。有一个可以在信号处理程序中安全调用的调用列表。幸运的是,sigsuspend()似乎就是其中之一。

更新:我这里有可用的代码:

代码语言:javascript
复制
//Filename: pthread_pause.c
//Author: Tomas 'Harvie' Mudrunka 2021
//Build: CFLAGS=-lpthread make pthread_pause; ./pthread_pause
//Test: valgrind --tool=helgrind ./pthread_pause

//I've wrote this code as excercise to solve following stack overflow question:
// https://stackoverflow.com/questions/9397068/how-to-pause-a-pthread-any-time-i-want/68119116#68119116

#define _GNU_SOURCE //pthread_yield() needs this
#include <signal.h>
#include <pthread.h>
//#include <pthread_extra.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>
#include <time.h>

#define PTHREAD_XSIG_STOP (SIGRTMIN+0)
#define PTHREAD_XSIG_CONT (SIGRTMIN+1)
#define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal

pthread_t main_thread;
sem_t pthread_pause_sem;
pthread_once_t pthread_pause_once_ctrl = PTHREAD_ONCE_INIT;

void pthread_pause_once(void) {
    sem_init(&pthread_pause_sem, 0, 1);
}

#define pthread_pause_init() (pthread_once(&pthread_pause_once_ctrl, &pthread_pause_once))

#define NSEC_PER_SEC (1000*1000*1000)
// timespec_normalise() from https://github.com/solemnwarning/timespec/
struct timespec timespec_normalise(struct timespec ts)
{
    while(ts.tv_nsec >= NSEC_PER_SEC) {
        ++(ts.tv_sec); ts.tv_nsec -= NSEC_PER_SEC;
    }
    while(ts.tv_nsec <= -NSEC_PER_SEC) {
        --(ts.tv_sec); ts.tv_nsec += NSEC_PER_SEC;
    }
    if(ts.tv_nsec < 0) { // Negative nanoseconds isn't valid according to POSIX.
        --(ts.tv_sec); ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec);
    }
    return ts;
}

void pthread_nanosleep(struct timespec t) {
    //Sleep calls on Linux get interrupted by signals, causing premature wake
    //Pthread (un)pause is built using signals
    //Therefore we need self-restarting sleep implementation
    //IO timeouts are restarted by SA_RESTART, but sleeps do need explicit restart
    //We also need to sleep using absolute time, because relative time is paused
    //You should use this in any thread that gets (un)paused

    struct timespec wake;
    clock_gettime(CLOCK_MONOTONIC, &wake);

    t = timespec_normalise(t);
    wake.tv_sec += t.tv_sec;
    wake.tv_nsec += t.tv_nsec;
    wake = timespec_normalise(wake);

    while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wake, NULL)) if(errno!=EINTR) break;
    return;
}
void pthread_nsleep(time_t s, long ns) {
    struct timespec t;
    t.tv_sec = s;
    t.tv_nsec = ns;
    pthread_nanosleep(t);
}

void pthread_sleep(time_t s) {
    pthread_nsleep(s, 0);
}

void pthread_pause_yield() {
    //Call this to give other threads chance to run
    //Wait until last (un)pause action gets finished
    sem_wait(&pthread_pause_sem);
    sem_post(&pthread_pause_sem);
        //usleep(0);
    //nanosleep(&((const struct timespec){.tv_sec=0,.tv_nsec=1}), NULL);
    //pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep
    pthread_yield();
}

void pthread_pause_handler(int signal) {
    //Do nothing when there are more signals pending (to cleanup the queue)
    //This is no longer needed, since we use semaphore to limit pending signals
    /*
    sigset_t pending;
    sigpending(&pending);
    if(sigismember(&pending, PTHREAD_XSIG_STOP)) return;
    if(sigismember(&pending, PTHREAD_XSIG_CONT)) return;
    */

    //Post semaphore to confirm that signal is handled
    sem_post(&pthread_pause_sem);
    //Suspend if needed
    if(signal == PTHREAD_XSIG_STOP) {
        sigset_t sigset;
        sigfillset(&sigset);
        sigdelset(&sigset, PTHREAD_XSIG_STOP);
        sigdelset(&sigset, PTHREAD_XSIG_CONT);
        sigsuspend(&sigset); //Wait for next signal
    } else return;
}

void pthread_pause_enable() {
    //Having signal queue too deep might not be necessary
    //It can be limited using RLIMIT_SIGPENDING
    //You can get runtime SigQ stats using following command:
    //grep -i sig /proc/$(pgrep binary)/status
    //This is no longer needed, since we use semaphores
    //struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
    //setrlimit(RLIMIT_SIGPENDING, &sigq);

    pthread_pause_init();

    //Prepare sigset
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, PTHREAD_XSIG_STOP);
    sigaddset(&sigset, PTHREAD_XSIG_CONT);

    //Register signal handlers
    //signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
    //signal(PTHREAD_XSIG_CONT, pthread_pause_handler);
    //We now use sigaction() instead of signal(), because it supports SA_RESTART
    const struct sigaction pause_sa = {
        .sa_handler = pthread_pause_handler,
        .sa_mask = sigset,
        .sa_flags = SA_RESTART,
        .sa_restorer = NULL
    };
    sigaction(PTHREAD_XSIG_STOP, &pause_sa, NULL);
    sigaction(PTHREAD_XSIG_CONT, &pause_sa, NULL);

    //UnBlock signals
    pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
}

void pthread_pause_disable() {
    //This is important for when you want to do some signal unsafe stuff
    //Eg.: locking mutex, calling printf() which has internal mutex, etc...
    //After unlocking mutex, you can enable pause again.

    pthread_pause_init();

    //Make sure all signals are dispatched before we block them
    sem_wait(&pthread_pause_sem);

    //Block signals
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, PTHREAD_XSIG_STOP);
    sigaddset(&sigset, PTHREAD_XSIG_CONT);
    pthread_sigmask(SIG_BLOCK, &sigset, NULL);

    sem_post(&pthread_pause_sem);
}


int pthread_pause(pthread_t thread) {
    sem_wait(&pthread_pause_sem);
    //If signal queue is full, we keep retrying
    while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000);
    pthread_pause_yield();
    return 0;
}

int pthread_unpause(pthread_t thread) {
    sem_wait(&pthread_pause_sem);
    //If signal queue is full, we keep retrying
    while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000);
    pthread_pause_yield();
    return 0;
}

void *thread_test() {
    //Whole process dies if you kill thread immediately before it is pausable
    //pthread_pause_enable();
    while(1) {
    //Printf() is not async signal safe (because it holds internal mutex),
    //you should call it only with pause disabled!
    //Will throw helgrind warnings anyway, not sure why...
    //See: man 7 signal-safety
    pthread_pause_disable();
        printf("Running!\n");
    pthread_pause_enable();

    //Pausing main thread should not cause deadlock
    //We pause main thread here just to test it is OK
    pthread_pause(main_thread);
    //pthread_nsleep(0, 1000*1000);
    pthread_unpause(main_thread);

    //Wait for a while
    //pthread_nsleep(0, 1000*1000*100);
    pthread_unpause(main_thread);
    }
}

int main() {
    pthread_t t;
    main_thread = pthread_self();
    pthread_pause_enable(); //Will get inherited by all threads from now on
    //you need to call pthread_pause_enable (or disable) before creating threads,
    //otherwise first (un)pause signal will kill whole process
    pthread_create(&t, NULL, thread_test, NULL);

    while(1) {
        pthread_pause(t);
        printf("PAUSED\n");
        pthread_sleep(3);

        printf("UNPAUSED\n");
        pthread_unpause(t);
        pthread_sleep(1);

    /*
    pthread_pause_disable();
        printf("RUNNING!\n");
    pthread_pause_enable();
    */
    pthread_pause(t);
    pthread_unpause(t);
    }

    pthread_join(t, NULL);
    printf("DIEDED!\n");
}

我也在开发名为"pthread_extra“的库,它会有类似这样的东西,甚至更多。很快就会出版。

UPDATE2:当快速调用pause/unpause (删除了sleep()调用)时,这仍然会导致死锁。glibc中的Printf()实现具有互斥量,因此如果您挂起了printf()中间的线程,然后想要从您的线程中打印出printf(),而您的线程计划稍后取消暂停该线程,这是永远不会发生的,因为printf()是锁定的。不幸的是,我删除了printf(),在线程中只运行空的while循环,但在高暂停/取消暂停的情况下仍然会出现死锁。我也不知道为什么。也许(甚至是实时的) Linux信号并不是100%安全的。有一个实时信号队列,也许它只是溢出或者别的什么。

UPDATE3:我想我已经设法修复了死锁,但必须完全重写大部分代码。现在,我为每个线程都有一个(sig_atomic_t)变量,用于保存该线程是否应该运行的状态。它的工作原理有点像条件变量。pthread_(un)pause()会为每个线程透明地记住这一点。我没有两个信号。现在我只有一个信号了。该信号的处理程序查看该变量,并且仅在sigsuspend()上阻塞该变量,当该变量指示线程不应运行时。否则,它将从信号处理程序返回。为了挂起/恢复线程,我现在将sig_atomic_t变量设置为所需的状态,并调用该信号(这对于挂起和恢复都是常见的)。使用实时信号以确保处理程序在修改状态变量后将实际运行,这一点很重要。由于线程状态数据库,代码有点复杂。我将在单独的解决方案中分享代码,一旦我设法使其足够简化。但我想在这里保留两个信号版本,因为它有点工作,我喜欢它的简单性,也许人们会给我们更多关于如何优化它的见解。

UPDATE4:我已经修复了原始代码中的死锁(不需要助手变量来保存状态),方法是对两个信号使用一个处理程序,并稍微优化一下信号队列。helgrind显示的printf()仍然有一些问题,但这不是由我的信号引起的,即使我根本不调用pause/unpause,它也会发生。总体而言,这只在LINUX上进行了测试,不确定代码的可移植性,因为似乎有一些信号处理程序的未记录行为最初导致了死锁。

请注意,暂停/取消暂停不能嵌套。如果暂停3次,取消暂停1次,线程将运行。如果你需要这样的行为,你应该创建某种类型的包装器,它将计算嵌套级别并相应地向线程发送信号。

UPDATE5:我通过以下更改提高了代码的健壮性:我通过使用信号量确保了暂停/取消暂停调用的正确序列化。这有望修复最后一个剩余的死锁。现在你可以确定当pause调用返回时,目标线程实际上已经暂停了。这也解决了信号队列溢出的问题。此外,我还添加了SA_RESTART标志,它可以防止内部信号导致IO等待中断。休眠/延迟仍然需要手动重新启动,但我提供了一个方便的包装器pthread_nanosleep(),它就是这样做的。

UPDATE6:我意识到简单地重启nanosleep()是不够的,因为在线程暂停时,超时不会运行。因此,我修改了pthread_nanosleep(),将超时间隔转换为未来的绝对时间点,并在此之前休眠。我还隐藏了信号量初始化,所以用户不需要这样做。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9397068

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档