【问题标题】:Linux Threads suspend/resumeLinux 线程暂停/恢复
【发布时间】:2012-07-13 04:15:06
【问题描述】:

我正在编写一个代码,其中有两个并行运行的线程。

1st 是启动第二个线程的主线程。 第二个线程只是一个执行空while循环的简单线程。

现在我想暂停/暂停创建它的第一个线程执行第二个线程。 一段时间后,我想从暂停/暂停的位置恢复第二个线程的执行(通过发出一些命令或函数)。

【问题讨论】:

    标签: c linux multithreading pthreads


    【解决方案1】:

    不确定你是否会喜欢我的回答。但是您可以通过这种方式实现。

    如果它是一个单独的进程而不是线程我有一个解决方案(这甚至可能适用于线程,也许有人可以分享你的想法)使用信号

    目前没有系统可以暂停或恢复进程的执行。 但你肯定可以建造一个。

    如果我想在我的项目中使用它,我会采取的步骤:

    • 为第二个进程注册一个信号处理程序。
    • 在信号处理程序中,等待一个信号量。
    • 当您想暂停其他进程时,只需发送一个信号
      您注册了另一个进程。该计划将进入 睡眠状态。
    • 当你想恢复进程时,你可以发送不同的信号 再次。在该信号处理程序中,您将检查信号量是否 锁定与否。如果它被锁定,您将释放信号量。所以
      进程 2 将继续执行。

    如果您可以实现这一点,请分享您的反馈,无论它是否适合您。谢谢。

    【讨论】:

    • 这可能没问题,但这取决于你在做什么。对于线程的大多数用途,条件变量是线程间同步的适当手段。如果您出于某种原因真的必须在您正在做的任何事情的中间暂停一个线程,那么这可能是合理的。但是OP没有说明这样的原因。
    • 与发送 SIGSTOP 的问题相同(正如我上面所指出的,无需编写处理程序即可工作)。你无法控制它何时发生或线程处于什么状态。如果它当时持有锁,你就有麻烦了。
    【解决方案2】:

    我尝试使用信号暂停和恢复线程,这是我的解决方案。请用-pthread编译链接。

    信号 SIGUSR1 通过调用 pause() 挂起线程,SIGUSR2 恢复线程。

    来自暂停的手册页:

    pause() 导致调用进程(或线程)休眠,直到 发出的信号要么终止进程,要么导致 调用一个 信号捕捉功能。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <signal.h>
    
    // Since I have only 2 threads so using two variables, 
    // array of bools will be more useful for `n` number of threads.
    static int is_th1_ready = 0;
    static int is_th2_ready = 0;
    
    static void cb_sig(int signal)
    {
            switch(signal) {
            case SIGUSR1:
                    pause();
                    break;
            case SIGUSR2:
                    break;
            }
    }
    
    static void *thread_job(void *t_id)
    {
            int i = 0;
            struct sigaction act;
    
            pthread_detach(pthread_self());
            sigemptyset(&act.sa_mask);
            act.sa_flags = 0;
            act.sa_handler = cb_sig;
    
            if (sigaction(SIGUSR1, &act, NULL) == -1) 
                    printf("unable to handle siguser1\n");
            if (sigaction(SIGUSR2, &act, NULL) == -1) 
                    printf("unable to handle siguser2\n");
    
            if (t_id == (void *)1)
                is_th1_ready = 1;
            if (t_id == (void *)2)
                is_th2_ready = 1;
    
            while (1) {
                    printf("thread id: %p, counter: %d\n", t_id, i++);
                    sleep(1);
            }
    
            return NULL;
    }
    
    int main()
    {
            int terminate = 0;
            int user_input;
            pthread_t thread1, thread2;
    
            pthread_create(&thread1, NULL, thread_job, (void *)1);
            // Spawned thread2 just to make sure it isn't suspended/paused 
            // when thread1 received SIGUSR1/SIGUSR2 signal
            pthread_create(&thread2, NULL, thread_job, (void *)2);
    
            while (!is_th1_ready && !is_th2_ready);
    
            while (!terminate) {
                    // to test, I am sensing signals depending on input from STDIN
                    printf("0: pause thread1, 1: resume thread1, -1: exit\n");
                    scanf("%d", &user_input);
    
                    switch(user_input) {
                    case -1: 
                            printf("terminating\n");
                            terminate = 1;
                            break;
                    case 0:
                            printf("raising SIGUSR1 to thread1\n");
                            pthread_kill(thread1, SIGUSR1);
                            break;
                    case 1:
                            printf("raising SIGUSR2 to thread1\n");
                            pthread_kill(thread1, SIGUSR2);
                            break;
                    }
            }
    
            pthread_kill(thread1, SIGKILL);
            pthread_kill(thread2, SIGKILL);
    
            return 0;
    }
    

    【讨论】:

    • 这个例子有很多问题。它不是线程安全的:在调用 pthread_kill 之前,不要等到 thread1 和 thread2 设置了它们的信号处理程序。如果scanf 从实际的人类 bean 中读取,输入图形终端,这可能无关紧要。但是一旦有人用重定向的标准输入运行这个程序,你可能会在发送 SIGUSR1 什么都不做的情况下运行(因为一个线程还没有安装它的信号处理程序)。更重要的是,您的程序有时可能会死锁,因为 printf 不是异步信号安全的,并且您是从信号处理程序调用它。
    • ...此外,即使您从 cb_sig 中删除 printf,您也会发现,暂停 thread1 有时会神秘地暂停 thread2 甚至锁定您的整个程序。这是因为printf 不是异步信号安全的——它需要许多锁(堆锁、stdio 锁),并且在对printf 的调用返回之前不会释放它们。当信号到达 printf 释放它的锁之前,你的整个程序实际上不能调用printf/scanf/malloc 等:他们使用的锁被现在挂起的线程和将阻止调用者,直到被挂起者释放它们(所以永远不会)。
    • 感谢您的审核,printf 仅用于调试,因此可以将其删除。我会确保线程在执行任何操作之前准备好。 @user1643723 在您的第二条评论中,第一行和第二行相互矛盾,您说在第一行中删除 printf,在第二行中您说问题是因为在信号处理程序中有 printf。你能详细说明一下吗?
    • 我使用scanf 只是为了测试我的代码,最终这应该是事件驱动的,如果出现某些情况可能会暂停线程或恢复它。我认为scanf 是测试它的最简单方法。 @user1643723,如果您对测试代码有任何建议,请告诉我。
    • 您应该了解“异步信号安全”的含义。一旦你完全理解了这一点,你就会看到,为了成功地暂停(并恢复!)带有信号的线程,该线程必须仅限于异步信号安全代码。提示:异步信号安全是整个代码的属性,它不仅适用于信号处理程序和系统库函数。实际上,为了编写异步信号安全代码,您必须仅使用异步信号安全库函数、系统调用、原子数据结构或堆栈上的数据结构。没有别的办法。
    【解决方案3】:

    你可以简单地通过信号挂起一个线程

    pthread_mutex_t mutex;
    static void thread_control_handler(int n, siginfo_t* siginfo, void* sigcontext) {
        // wait time out
        pthread_mutex_lock(&mutex);
        pthread_mutex_unlock(&mutex);
    }
    // suspend a thread for some time
    void thread_suspend(int tid, int time) {
        struct sigaction act;
        struct sigaction oact;
        memset(&act, 0, sizeof(act));
        act.sa_sigaction = thread_control_handler;
        act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
        sigemptyset(&act.sa_mask);
        pthread_mutex_init(&mutex, 0);
        if (!sigaction(SIGURG, &act, &oact)) {
            pthread_mutex_lock(&mutex);
            kill(tid, SIGURG);
            sleep(time);
            pthread_mutex_unlock(&mutex);
        }
    }
    

    【讨论】:

      【解决方案4】:

      这个问题不是关于如何使用互斥锁,而是如何挂起一个线程。

      在 Unix 规范中有一个线程函数叫做 pthread_suspend,另一个叫做 pthread_resume_np,但是由于某种原因,制作 Linux、FreeBSD、NetBSD 等的人没有实现这些函数。

      所以要理解它,功能根本不存在。有一些解决方法,但不幸的是它与在 Windows 上调用 SuspendThread 不同。你必须做各种不可移植的事情才能让线程停止并开始使用信号。

      停止和恢复线程对于调试器和垃圾收集器至关重要。例如,我见过一个无法正确实现“SuspendThread”功能的 Wine 版本。因此任何使用它的 Windows 程序都无法正常工作。

      我认为基于 JVM 将这种信号技术用于垃圾收集器这一事实,可以正确使用信号来执行此操作,但我也刚刚在网上看到一些文章,其中人们注意到死锁等等JVM,有时无法重现。

      所以为了回答这个问题,除非你有一个很好的 Unix 来实现 pthread_suspend_np,否则你不能正确地挂起和恢复 Unix 的线程。否则你会被信号卡住。

      信号的最大问题是当您有大约五个不同的库都链接到同一个程序并且都试图同时使用相同的信号时。出于这个原因,我相信您实际上不能在一个程序中使用 ValGrind 之类的东西,例如 Boehm GC。至少在用户空间的最低级别没有主要编码。

      这个问题的另一个答案可能是。像 Linuz Torvalds 对 NVidia 所做的那样,向他指指点点,让他实现 Linux 中缺少的两个最关键的部分。首先,pthread_suspend,其次,内存页面上的脏位,以便可以实现适当的垃圾收集器。在网上发起一个大型请愿书,并不断地动动手指。也许到 Windows 20 出来的时候,他们会意识到暂停和恢复线程,还有脏位实际上是 Windows 和 Mac 优于 Linux 或任何不实现 pthread_suspend 的 Unix 的根本原因之一,也是脏位在虚拟页面上,就像在 Windows 中的 VirtualAlloc 一样。

      我没有希望。实际上,对我来说,我花了数年时间围绕为 Linux 构建东西来规划我的未来,但放弃了希望作为可靠的东西,这一切似乎都取决于虚拟内存的脏位的可用性,以及干净地挂起线程。

      【讨论】:

      • 第 1 部分)在 Linux 中挂起线程很容易。没有“死锁的危险”,它只是有效。 恢复线程(然后让应用程序正常运行)是困难的部分。由于“安全点”系统,Java 和其他流行的 VM 语言可以摆脱它——而不是在任何地方突然停止,线程在预定义的位置将控制权交给 GC:内部循环,进入或退出本机方法等. 另外,Java默认是可重入锁(比如javascript,根本没有锁),所以死锁的空间不大。
      • 第 2 部分)大多数“实现”挂起线程的系统都无法避免死锁,它们只是将责任推给用户。 “SuspendThread”也不是完全安全的,它只是假设你知道你在做什么。 Linux 不应该因为缺乏支持而受到指责——事实上,大多数系统调用都是可中断/可重入的,这就是信号起作用的原因。用户空间库(最著名的是 libc malloc 和 thread-local-storage)在重入和取消方面是最大的违规者。
      • VxWorks(不是 Unix)实现了 taskSuspend 和 taskResume。他们的实现可能会提供一些想法。
      【解决方案5】:

      为了在线程上实现暂停,您需要让它等待某个事件发生。等待自旋锁互斥体是在浪费 CPU 周期。恕我直言,不应遵循此方法,因为 CPU 周期可能已被其他进程/线程用完。 等待非阻塞描述符(管道、套接字或其他)。可以在这里看到使用pipes for inter-thread communication 的示例代码 如果您的第二个线程具有来自多个来源的更多信息,而不仅仅是暂停和恢复信号,则上述解决方案很有用。顶级 select/poll/epoll 可用于非阻塞描述符。您可以指定 select/poll/epoll 系统调用的等待时间,只会浪费那么多微秒的 CPU 周期。 我以前瞻性的方式提到了这个解决方案,即您的第二个线程将处理更多的事情或事件,而不仅仅是暂停和恢复。对不起,如果它比你问的更详细。

      另一种更简单的方法是在这些线程之间共享一个布尔变量。 主线程是变量的写入者,0 - 表示停止。 1 - 表示简历 第二个线程只读取变量的值。要实现“0”状态,请使用 usleep 几微秒,然后再次检查该值。假设您的设计可以接受几微秒的延迟。 要实现'1' - 在执行一定数量的操作后检查变量的值。 否则,您还可以实现从“1”状态移动到“0”状态的信号。

      【讨论】:

        【解决方案6】:

        如果您可以改用进程,则可以将作业控制信号 (SIGSTOP / SIGCONT) 发送到第二个进程。如果您仍想在这些进程之间共享内存,可以使用 SysV 共享内存(shmop、shmget、shmctl...)。

        即使我自己没有尝试过,也可以使用较低级别的 clone() 系统调用来生成不共享信号的线程。有了它,您也许可以将 SIGSTOP 和 SIGCONT 发送到另一个线程。

        【讨论】:

          【解决方案7】:

          POSIX 中没有 pthread_suspend()、pthread_resume() 类型的 API。
          大多数条件变量可用于控制其他线程的执行。

          条件变量机制允许线程暂停执行 并放弃处理器,直到某些条件为真。一个条件 变量必须始终与互斥锁相关联以避免竞争 由一个准备等待的线程和另一个线程创建的条件 这可能会在第一个线程实际等待之前发出条件信号 导致死锁。

          更多信息

          Pthreads

          Linux Tutorial Posix Threads

          【讨论】:

            【解决方案8】:

            您可以使用互斥锁来做到这一点,伪代码是:

            While (true) {
                /* pause resume */
                lock(my_lock); /* if this is locked by thread1, thread2 will wait until thread1 */
                               /* unlocks it */
                unlock(my_lock); /* unlock so that next iteration thread2 could lock */
            
                /* do actual work here */
            }
            

            【讨论】:

            • 使用这个解决方案我必须在第二个线程中添加这个检查,但我想要的是第一个线程应该控制第二个线程的执行,第二个线程只是做自己的工作,即它不应该包含对互斥锁等的任何检查
            • 我也有同样的想法,甚至发现我认为 pthread_kill 函数应该作为进程的终止函数 stackoverflow.com/questions/11046720/…
            【解决方案9】:

            据我所知,您不能真正使用 pthread 暂停其他线程。你必须在你的第二个线程中有一些东西来检查它应该使用条件变量之类的东西暂停的时间。这是做这类事情的标准方法。

            【讨论】:

            • 我不知道你能不能做到,我只是在评论pthreads。
            • +1 ... 并补充说:即使你可以,那么你不应该挂起线程。始终等待条件变量或类似的同步原语,永远不要做任何不同的事情。暂停(或更糟糕的是,杀死)线程会导致巨大的邪恶。使用同步原语获得死锁或意外结果是相当困难/容易的。然而,在挂起(或杀死)线程时,这是一件正常的事情。而且几乎不可能调试...
            • @FrancisUpton 在信号量变量的帮助下睡觉没有暂停吗?
            • @happy2Help 不确定你的意思,但我的意思是你不能显式地暂停 pthreads 中的 another 线程。当然,由于各种原因,线程会一直暂停。
            • 嗯,你有点可以做到这一点,使用pthread_kill(...SIGSTOP),虽然没有明确的支持,这将做到这一点。但我强烈建议不要这样做。挂起一个线程会停止它的执行,无论它在哪里,无论它在做什么(例如它可能持有一个锁,或者有一半完成的数据)。在 condvar/semaphore 上的阻塞以一种受控的方式在众所周知的时间点以众所周知的状态停止执行。没有惊喜,没有未知的副作用。
            猜你喜欢
            • 1970-01-01
            • 2015-02-21
            • 1970-01-01
            • 1970-01-01
            • 2016-03-24
            • 1970-01-01
            • 2010-12-28
            • 2020-08-13
            • 2011-08-02
            相关资源
            最近更新 更多