如何暂停pthread我想要的任何时间?

时间:2012-02-22 14:49:49

标签: c++ linux embedded pthreads

最近我开始将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可以像任何时候暂停和恢复的过程一样,请告诉我一些相关的功能,我可以自己搞清楚。如果不能,我想我应该关注旧的解决方案。非常感谢。

4 个答案:

答案 0 :(得分:4)

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

Google for ThreadPThread.m3并查看惯例" StopWorld"和" StartWorld"。处理程序本身位于ThreadPThreadC.c中。

答案 1 :(得分:2)

如果在条件变量不足的特定点停止,则不能使用pthreads执行此操作。 pthread接口不包括挂起/恢复功能。

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

  

POSIX标准没有提供任何机制,线程A可以通过该机制暂停另一​​个线程B的执行,而无需B的协作。实现挂起/重启机制的唯一方法是让B定期检查挂起的某个全局变量请求然后暂停一个条件变量,另一个线程可以稍后发出信号重新启动B.

该FAQ的答案继续描述了一些非标准的方法,一个在Solaris中,另一个在LinuxThreads中(现在已经过时了;不要将它与Linux上的当前线程混淆);这些都不适用于你的情况。

答案 2 :(得分:1)

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

免责声明:我还没有尝试过。但理论上它应该可以工作。

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

更新:我这里有工作代码:

//Filename: pause.c
//Author: Tomas 'Harvie' Mudrunka 2021
//Build: CFLAGS=-lpthread make pause; ./pause
//Test: valgrind --tool=helgrind ./pause

#include <signal.h>
#include <pthread.h>
//#include <pthread_extra.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>

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

void pthread_pause_handler(int signal) {
    //Do nothing when there are more signals pending (to cleanup the queue)
    sigset_t pending;
    sigpending(&pending);
    if(sigismember(&pending, PTHREAD_XSIG_STOP)) return;
    if(sigismember(&pending, PTHREAD_XSIG_CONT)) return;

    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
    struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
    setrlimit(RLIMIT_SIGPENDING, &sigq);

    //Register signal handlers
    signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
    signal(PTHREAD_XSIG_CONT, pthread_pause_handler);

    //UnBlock signals
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, PTHREAD_XSIG_STOP);
    sigaddset(&sigset, PTHREAD_XSIG_CONT);
    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.

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


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

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

void *thread_test() {
    //Whole process dies if you kill thread immediately before it is pausable
    //pthread_pause_enable();
    while(1) {
        usleep(1000*300);
    //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();
    }
}

int main() {
    pthread_t t;
    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 signal will kill whole process
    pthread_create(&t, NULL, thread_test, NULL);

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

        printf("UNPAUSED\n");
        pthread_unpause(t);
        sleep(1);
    }

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

我也在开发名为“pthread_extra”的库,它将包含诸如此类的内容以及更多内容。即将发布。

UPDATE2:这在快速调用暂停/取消暂停时仍然会导致死锁(删除了 sleep() 调用)。 glibc 中的 Printf() 实现具有互斥锁,因此如果您挂起 printf() 中间的线程,然后想从计划稍后取消暂停该线程的线程中执行 printf(),它将永远不会发生,因为 printf() 是锁定。不幸的是,我已经删除了 printf() 并且只在线程中运行空的 while 循环,但是我仍然在高暂停/取消暂停率下遇到死锁。我不知道为什么。也许(甚至是实时)Linux 信号不是 100% 安全的。有实时信号队列,可能只是溢出什么的...

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

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

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

答案 3 :(得分:0)

以下是具有暂停/恢复功能的类中的线程函数示例...

class SomeClass
{
public:
    // ... construction/destruction

    void Resume();
    void Pause();
    void Stop();

private:
    static void* ThreadFunc(void* pParam);

    pthread_t thread;
    pthread_mutex_t mutex;
    pthread_cond_t cond_var;
    int command;
};

SomeClass::SomeClass()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond_var, NULL);

    // create thread in suspended state..
    command = 0;
    pthread_create(&thread, NULL, ThreadFunc, this);
}

SomeClass::~SomeClass()
{
    // we should stop the thread and exit ThreadFunc before calling of blocking pthread_join function
    // also it prevents the mutex staying locked..
    Stop();
    pthread_join(thread, NULL);

    pthread_cond_destroy(&cond_var);
    pthread_mutex_destroy(&mutex);
}

void* SomeClass::ThreadFunc(void* pParam)
{
    SomeClass* pThis = (SomeClass*)pParam;
    timespec time_ns = {0, 50*1000*1000};   // 50 milliseconds

    while(1)
    {
        pthread_mutex_lock(&pThis->mutex);

        if (pThis->command == 2) // command to stop thread..
        {
            // be sure to unlock mutex before exit..
            pthread_mutex_unlock(&pThis->mutex);
            return NULL;
        }
        else if (pThis->command == 0) // command to pause thread..
        {
            pthread_cond_wait(&pThis->cond_var, &pThis->mutex);
            // dont forget to unlock the mutex..
            pthread_mutex_unlock(&pThis->mutex);
            continue;
        }

        if (pThis->command == 1) // command to run..
        {
            // normal runing process..
            fprintf(stderr, "*");
        }

        pthread_mutex_unlock(&pThis->mutex);

        // it's important to give main thread few time after unlock 'this'
        pthread_yield();
        // ... or...
        //nanosleep(&time_ns, NULL);
    }
    pthread_exit(NULL);
}

void SomeClass::Stop()
{
    pthread_mutex_lock(&mutex);
    command = 2;
    pthread_cond_signal(&cond_var);
    pthread_mutex_unlock(&mutex);
}

void SomeClass::Pause()
{
    pthread_mutex_lock(&mutex);
    command = 0;
    // in pause command we dont need to signal cond_var because we not in wait state now..
    pthread_mutex_unlock(&mutex);
}

void SomeClass::Resume()
{
    pthread_mutex_lock(&mutex);
    command = 1;
    pthread_cond_signal(&cond_var);
    pthread_mutex_unlock(&mutex);
}