等待线程退出条件的正确方法

Posted

技术标签:

【中文标题】等待线程退出条件的正确方法【英文标题】:Proper method to wait for exit conditions in threads 【发布时间】:2020-03-23 18:30:32 【问题描述】:

所以阅读this topic,常见的退出方式是使用标志。我的问题是,如何处理等待?假设线程仅每 30 秒运行一次,您将如何正确等待这 30 秒?

使用sem_timedwait() 并不理想,因为它依赖于系统时钟,对时钟的任何更改都会严重影响您的应用程序。 This topic 解释使用条件变量。问题是,它依赖于互斥体。您不能在信号处理程序中安全地使用pthread_mutex_lock() 和pthread_mutex_unlock()。所以就我上面 30 年代的例子来说,如果你想立即退出,谁在处理互斥锁解锁?

我的猜测是另一个线程,其唯一目的是检查退出标志,如果为真,它将解锁互斥锁。但是,那条线是什么样的?只是坐在那里不断检查旗帜不是浪费资源吗?例如,您会使用sleep() 并检查每1 秒吗?

我不相信我的猜测是正确的。这似乎非常低效,我遇到了类似的“我该如何等待”类型的问题。我觉得我错过了一些东西,但我的搜索导致的主题类似于我链接的关于标志的内容,但没有等待。

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

pthread_mutex_t my_mutex;
volatile sig_atomic_t exitRequested = 0;

void signal_handler(int signum) 
    exitRequested = 1;


bool my_timedwait(pthread_mutex_t *mutex, int seconds) 
    pthread_condattr_t attr;
    pthread_condattr_init(&attr);
    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);

    pthread_cond_t cond;
    pthread_cond_init(&cond, &attr);

    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    ts.tv_sec += seconds;

    int status = pthread_cond_timedwait(&cond, mutex, &ts);
    if (status == 0) 
        return false; // mutex unlocked
    

    if ((status < 0) && (status != ETIMEDOUT)) 
        // error, do something
        return false;
    

    return true; // timedout


void *exitThread(void *ptr) 
    // constant check???
    while (1) 
      if (exitRequested) 
          pthread_mutex_unlock(&my_mutex);
          break;
      
    


void *myThread(void *ptr) 
    while (1) 
        // do work
        printf("test\n");

        // wait and check for exit (how?)
        if (!my_timedwait(&my_mutex, 30)) 
            // exiting
            break;
        
    


int main(void) 
    // init and setup signals
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigaction(SIGINT, &sa, NULL);

    // init the mutex and lock it
    pthread_mutex_init(&my_mutex, NULL);
    pthread_mutex_lock(&my_mutex);

    // start exit thread
    pthread_t exitHandler;
    pthread_create(&exitHandler, NULL, exitThread, NULL);

    // start thread
    pthread_t threadHandler;
    pthread_create(&threadHandler, NULL, myThread, NULL);

    // wait for thread to exit
    pthread_join(threadHandler, NULL);
    pthread_join(exitHandler, NULL);

    return EXIT_SUCCESS;

【问题讨论】:

信号处理程序与什么有什么关系?为什么要在信号处理程序中等待。使用pthread_cond_timewait 将是执行此操作的典型方法。 您能否提供一些代码来说明您正在尝试做的事情,并描述为什么您尝试的方法不起作用? @DavidSchwartz 一切?问题是如何等待退出条件。例如,当您按 Ctrl+C 时,如何将其传递给线程。当使用pthread_cond_timewait 时,它需要一个互斥锁,并且您不能在信号处理程序中使用互斥锁。 @jxh 确定我可以添加代码,但我不确定它如何帮助解决这个问题。问题不在于我所拥有的东西不起作用。就是我概述的内容不是很有效,而且看起来也不合适。我会添加一些代码。它基本上是编码的最后两段。 @F.Coz 如果您需要在多线程程序中处理外部信号,那么在除一个线程之外的所有线程中阻塞这些信号并拥有一个线程来等待信号是绝对值得的。这样,您就不需要信号处理程序了。 【参考方案1】:

解决方案很简单。而不是在pthread_join 中阻止第一个线程,而是阻止该线程等待信号。这将确保可以同步处理 SIGINT。

您需要一个受互斥体保护的全局结构。它应该计算未完成线程的数量以及是否请求关闭。

当线程完成时,让它获取互斥锁,减少未完成线程的数量,如果它为零,则发送SIGINT。主线程可以循环等待信号。如果是因为线程数变为零,则让进程终止。如果是来自外部信号,则设置shutdown 标志,广播条件变量,解锁互斥体,并继续等待线程数归零。

这是一个开始:

pthread_mutex_t my_mutex; // protects shared state
pthread_cond_t my_cond;   // allows threads to wait some time
bool exitRequested = 0;   // protected by mutex
int threadsRunning = 0;   // protected by mutex
pthread_t main_thread;    // set in main

bool my_timedwait(int seconds)

    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    ts.tv_sec += seconds;

    pthread_mutex_lock (&my_mutex);
    while (exitRequested == 0)
    
        int status = pthread_cond_timedwait(&my_cond, &my_mutex, &ts);
        if (status == ETIMEDOUT) // we waited as long as supposed to
            break;
    

    bool ret = ! exitRequested;
    pthread_mutex_unlock (&my_mutex);

    return ret; // timedout


bool shuttingDown()

    pthread_mutex_lock (&my_mutex);
    bool ret = exitRequested;
    pthread_mutex_unlock (&my_mutex);
    return ret;


void requestShutdown()

    // call from the main thread if a SIGINT is received
    pthread_mutex_lock (&my_mutex);
    exitRequested = 1;
    pthread_cond_broadcast (&my_cond);
    pthread_mutex_unlock (&my_mutex);


void threadDone()

    // call when a thread is done
    pthread_mutex_lock (&my_mutex);
    if (--threadsRunning == 0)
        pthread_kill(main_thread, SIGINT); // make the main thread end
    pthread_mutex_unlock (&my_mutex);

【讨论】:

"主线程可以循环等待信号"。这是主要问题,它看起来像什么?在我的示例中,它只是在 exitThread 中直接循环,这会耗尽 CPU。 @F.Coz 你的主线程阻塞在pthread_join。相反,阻止sigwait 我明白你现在在说什么,用sigwait 代替pthread_join。在您修改的my_timedwait 中,您正在锁定和解锁my_mutex。这是否意味着您只能调用该函数一次,否则超时将不断增加并变得更长,因为它们需要相互等待? 条件变量可用于等待谓词变为真,然后再进入临界区。如果谓词为假,则等待条件变量。相关联的互斥锁在等待时被释放,以便其他线程可以获取锁并取得进展,并希望更改谓词。当其他线程完成时,它会向条件变量发出信号并释放锁。服务员现在重新获取互斥体并再次检查谓词。 在等待条件变量后唤醒会导致再次获取关联的互斥锁。如果谓词仍然为假,则线程再次等待条件变量。如果谓词为真,线程可以继续持有锁。

以上是关于等待线程退出条件的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

处理 SIGTERM 后无法使用条件变量正确退出子进程

读者线程没有退出 - Posix Pthreads

使用完成信号退出 Qt 中的线程并进行 clean_up 的正确方法

linux线程的创建、退出、等待、取消、分离

多线程的创建退出等待删除语法

设置主线程等待子线程执行的方法