QT提供了QThread类来实现多线程编程。QThread::terminate()用来结束子线程。假如调用terminate()后,子线程不是立刻停止,程序员还可以调用QThread::wait()等待子线程结束,才进行后面的操作。在以前的项目里,我经常使用terminate + wait的组合来结束线程。但是前几天发现,这样的组合并非总能成功:子线程有时不能及时结束,造成主线程也卡在wait()处,整个程序不能运行。下面的小程序展示的是一个terminate + wait的例子。当然,这个程序是可以及时终止的,不会有子线程残留。这个程序只是展示terminate + wait的用法:
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include "thrd.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
thrd t;
t.start();
MainWindow w;
w.show();
return a.exec();
}
thrd.cpp
#include "thrd.h"
#include <QDebug>
thrd * g_pThrd = NULL;
thrd::thrd(QObject *parent) : QThread(parent)
{
g_pThrd = this;
}
void thrd::run()
{
while(true)
{
qDebug()<<"hello";
msleep(1000);
}
}
thrd * thrd::pGetInstance()
{
return g_pThrd;
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "thrd.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
thrd * p = thrd::pGetInstance();
p->terminate();
p->wait();
QMainWindow::closeEvent(event);
}
回到那个被子线程卡住的程序上来。经断点调试,发现子线程不能被及时终止,而是卡在了wait处。感觉是qt的terminate函数有时不好用。以前在MFC编程时,我的师傅也遇到过类似的问题。解决的办法时,不要在子线程里用while(true)这样的死循环。而是采用 while(bool)这种方式。主线程关闭子线程时,只要把布尔变量变为false即可。下面的例子就是这样的示范:
thrd.cpp
#include "thrd.h"
#include <QDebug>
Thrd * g_pThrd = NULL;
Thrd::Thrd(QObject *parent) : QThread(parent)
{
g_pThrd = this;
m_bPermission = false;
m_bRunning = false;
}
void Thrd::run()
{
m_bRunning = true;
while(m_bPermission)
{
qDebug()<<1;
msleep(1000);
}
qDebug()<<"finish";
m_bRunning = false;
}
void Thrd::vSetPermission(bool b)
{
m_bPermission = b;
}
void Thrd::vStop()
{
while(m_bRunning){}
}
Thrd * Thrd::pGetInstance()
{
return g_pThrd;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include "thrd.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Thrd thd;
thd.vSetPermission(true);
thd.m_bRunning = true;
thd.start();
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "thrd.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
Thrd * pThrd = Thrd::pGetInstance();
if(pThrd)
{
pThrd->vSetPermission(false);
pThrd->vStop();
}
QMainWindow::closeEvent(event);
}
在问题项目里采用了以上策略后,程序不再卡死。
有人可能会认为main.cpp中 thd.m_bRunning = true语句没必要,因为在run()函数一开始也赋值了m_bRunning
但是不要忘记,在微观尺度上,线程的调度很难保证均匀:主线程可能已经运行了几十条指令,但是子线程可能一步都没走;或者反之。假如没有main.cpp中 thd.m_bRunning = true一句,直接使用start()启动线程,那么子线程可能不会立刻启动,于是m_bRunning始终保持在false值。假如操作员“很快的”在程序启动后立刻关闭窗体,那么有这种可能--窗体关闭时,因为m_bRunning还是false,所以while(m_bRunning)立刻就过去了。于是,子线程在while(m_bRunning)之后才启动。假如程序员考虑不到这种情况,出于某种需要,在while(m_bRunning)之后又把m_bPemission设置为true,则子线程又有可能跑起来,而且程序员还蒙在鼓里。