线程,ansi c信号和Qt

时间:2012-06-20 09:40:07

标签: multithreading qt signals

我正在编写一个基于多线程插件的应用程序。我不会是插件作者。所以我希望避免主应用程序崩溃导致插件中的分段错误。可能吗?或者插件中的崩溃肯定也会影响主要的应用程序状态? 我用qt编写了一个草图程序,因为我的“真实”应用程序强烈基于qt库。就像你可以看到我强迫线程崩溃调用未分配的QString上的trimmed函数。正确调用了信号处理程序,但在线程被强制退出后,主应用程序也崩溃了。我做错什么了吗?或者就像我之前所说的那样,我想做的事情是不可能实现的? 请注意,在该程序的简化版本中,我避免使用插件但仅使用线程。我想,引入插件将增加一个新的关键级别。我想一步一步走。而且,总的来说,我想了解我的目标是否可行。非常感谢大家会给我的任何帮助或建议。

#include <QString>
#include <QThread>
#include<csignal>
#include <QtGlobal>
#include <QtCore/QCoreApplication>


class MyThread : public QThread
{
public:
    static void sigHand(int sig)
    {
        qDebug("Thread crashed");
        QThread* th = QThread::currentThread();
        th->exit(1);
    }

    MyThread(QObject * parent = 0)
    :QThread(parent)
    {
        signal(SIGSEGV,sigHand);
    }

    ~MyThread()
    {
        signal(SIGSEGV,SIG_DFL);
        qDebug("Deleted thread, restored default signal handler");
    }

    void run()
    {
        QString* s;
        s->trimmed();
        qDebug("Should not reach this point");
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyThread th(&a);
    th.run();
    while (th.isRunning());
    qDebug("Thread died but main application still on");
    return a.exec();
}

1 个答案:

答案 0 :(得分:1)

我目前正在处理同样的问题,并通过谷歌发现了这个问题。

您的来源无法正常工作有几个原因:

  • 没有新帖子。只有在调用QThread :: start时才会创建该线程。而是调用MyThread :: run,它在主线程中执行run方法。

  • 您调用QThread :: exit来停止线程,该线程不应该直接停止线程,而是向线程事件循环发送(qt)信号,请求它停止。由于既没有线程也没有事件循环,因此该函数无效。即使你已经调用了QThread :: start,它也行不通,因为编写run方法不会创建qt事件循环。为了能够使用任何QThread退出,您需要先调用QThread :: exec 但是,无论如何,QThread :: exit是错误的方法。为了防止SIGSEGV,必须立即调用线程,而不是在事件循环中接收到(qt)信号之后。因此,虽然通常不赞成,但在这种情况下,QThread :: terminate必须被称为

  • 但通常认为从信号处理程序调用QThread :: currentThread,QThread :: exit或QThread :: terminate等复杂函数是不安全的,所以你永远不应该在那里调用它们

  • 由于线程仍然在信号处理程序之后运行(我甚至不确定QThread :: terminate会足够快地杀死它),信号处理程序退出到调用它的位置,因此它重新执行导致SIGSEGV的指令,并发生下一个SIGSEGV。

因此我使用了不同的方法,信号处理程序将包含指令地址的寄存器更改为另一个函数,然后在信号处理程序退出后运行,而不是崩溃指令。像:

void signalHandler(int type, siginfo_t * si, void* ccontext){
    (static_cast<ucontext_t*>(ccontext))->Eip = &recoverFromCrash;
}

struct sigaction sa;
memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = &signalHandler; 
sigaction(SIGSEGV, &sa, 0);

然后,通常在导致SIGSEGV的线程中调用recoverFromCrash函数。由于为所有线程调用了所有SIGSEGV的信号处理程序,因此该函数必须检查它运行的是哪个线程。

但是,我认为简单地杀死线程并不安全,因为可能存在其他内容,具体取决于正在运行的线程。因此,不要杀死它,而是让它在无限循环中运行(调用sleep以避免浪费CPU时间)。然后,当程序关闭时,它设置一个全局变量,并终止该线程。 (注意,恢复函数必须永远不会返回,否则执行将返回到导致SIGSEGV的函数)

另一方面,从mainthread调用,它启动一个新的事件循环,让程序运行。

if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
    //sub thread
    QThread* t = QThread::currentThread();
    while (programIsRunning) ThreadBreaker::sleep(1);
    ThreadBreaker::forceTerminate();
} else {
    //main thread
    while (programIsRunning) {
        QApplication::processEvents(QEventLoop::AllEvents);
        ThreadBreaker::msleep(1);
    }
    exit(0);
}

ThreadBreaker是围绕QThread的一个简单的包装类,因为QThread的msleep,sleep和setTerminationEnabled(在终止之前必须被调用)受到保护,无法从recover函数调用。

但这只是基本情况。还有很多其他事情需要担心:捕获SIGFPE,捕获堆栈溢出(检查SIGSEGV的地址,在备用堆栈中运行信号处理程序),有一堆用于平台独立性的定义(64位,arm,mac) ),显示调试消息(尝试获取堆栈跟踪,想知道为什么调用gdb使其崩溃X服务器,想知道为什么调用glibc backtrace会导致程序崩溃)...