Qt中提供了對於線程的支持,它提供了一些獨立於平台的線程類,要進行多線程方法,可以有兩種方式。
1. 第一種方式
qt提供QThread類,在QThread類中有一個virtual函數QThread::run()。
要創建一個新的線程,我們只需定義一個MyThread類,讓其繼承QThread,然后重新實現QThread::run()。
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread();
protected:
//QThread的虛函數
//線程處理函數
//不能直接調用,通過start()間接調用
void run();
signals:
void isDone(); //處理完成信號
signals:
public slots:
};
#endif // MYTHREAD_H
};
然后可以在run中寫入要進行的操作,比如可以讓其等待5秒。若不是多線程,在運行時我們單擊窗口,窗口會出現無響應的狀態。
那如何通知線程結束?這就可以用qt的信號和槽機制了,我們可以在操作完成時發出一個完成信號,完成信號我們在聲明文件里已經定義了。
void MyThread::run()
{
//很復雜的數據處理
//需要耗時5秒
sleep(5);
emit isDone(); //發送完成信號
}
這樣,我們就把線程的操作寫完了。
現在,我們先來布一個簡單的ui,只用到了一個LcdNumber和一個PushButton。
在當前widget的頭文件中定義一些需要用到的操作。並加入我們定義的線程文件。
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QTimer>
#include "mythread.h" //線程頭文件
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
void dealTimeout(); //定時器槽函數
void dealDone(); //線程槽函數
void stopThread(); //停止線程
private slots:
void on_pushButton_clicked();
private:
Ui::MyWidget *ui;
QTimer *myTimer; //聲明變量
MyThread *thread; //線程對象
};
#endif // MYWIDGET_H
我們用一個timer定時器來讓Lcd控件按指定時間更新數字,當我們點擊開始按鈕時,定時器啟動,自動觸發timerout信號。捕獲timerout信號,在dealTimerout()函數中寫入需要進行的操作,當捕獲到timerout時,自動使用dealTimerout槽函數。
dealTimerout()我們可以這樣寫:
void MyWidget::dealTimeout()
{
static int i = 0;
i++;
//設置lcd的值
ui->lcdNumber->display(i);
}
在widget的構造函數中,先創建一個定時器並為線程函數分配空間:
myTimer = new QTimer(this);
thread = new QThread(thread); //分配空間
然后我們關聯信號和槽:
//只要定時器啟動,自動觸發timeout
connect(myTimer, &QTimer::timeout, this, &MyWidget::dealTimeout);
在開始按鈕的槽函數中,啟動定時器,並開啟線程,需要注意的是,我們不能直接調用run函數,要通過start()間接調用線程函數。
void MyWidget::on_pushButton_clicked()
{
//若定時器沒有工作
if(myTimer->isActive() == false)
{
myTimer->start(100);
}
//啟動線程,處理數據
thread->start();
}
線程結束時我們接收到isDone信號,我們在其中關閉定時器:
void MyWidget::dealDone()
{
qDebug() << "it is over"; //打印線程結束信息
myTimer->stop(); //關閉定時器
}
我們選擇在退出窗口時關閉線程,退出窗口時會觸發destroyed信號,線程關閉的槽函數實現如下:
void MyWidget::stopThread()
{
//停止線程
thread->quit();
//等待線程處理完手頭工作
thread->wait();
}
最后,在widget主窗口的構造函數中加入線程的關聯信號和槽:
connect(thread, &MyThread::isDone, this, &MyWidget::dealDone);
//當按窗口右上角x時,觸發destroyed信號
connect(this, &MyWidget::destroyed, this, &MyWidget::stopThread);
現在,當我們啟動程序時,窗口如下:
當我們點擊Start按鈕時,觸發定時器,開啟線程,每100ms更新一次Lcd中的數值:
5秒后,線程停止,發出isDone信號,執行dealDone槽函數,顯示it is over並關閉計時器:
再次點擊,再次啟動定時器,繼續累加數字並設置到Lcd中,點擊x,程序退出,停止線程。
2. 第二種方式
新建一個工程,ui如圖:
新建一個類,繼承自QObject,然后在類中設置一個線程函數。
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread();
//線程處理函數
void MyTimeout();
void setFlag(bool flag = true);
signals:
void mySignal();
public slots:
private:
bool isStop;
};
#endif // MYTHREAD_H
通過一個bool變量來控制線程結束,通過發出mySignal()信號來調用處理槽函數。
在widget中定義如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "mythread.h"
#include <QThread>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void dealSignal();
void dealClose();
signals:
void startThread(); //啟動子線程的信號
private slots:
void on_pushButtonStart_clicked();
void on_pushButtonStop_clicked();
private:
Ui::Widget *ui;
MyThread *myT;
QThread *thread;
};
#endif // WIDGET_H
兩個槽函數dealSignal()和dealClose()分別關聯着mySignal()和destroyed()信號。
信號startThread()用於啟動子線程。
在widget的實現中,
-
首先創建一個線程對象,需要注意不能指定父對象。
myT = new MyThread; -
創建一個Qthread子線程對象
QThread *thread = new QThread(this); -
把我們的自定義線程類,加入到子線程(若是myT指定了父對象,此處就會出錯。)
my->moveToThread(thread); -
啟動子線程,只是把線程開啟了,並沒有啟動線程處理函數
thread.start(); -
線程的啟動,必須通過signal - slot的方式。
各種函數實現如下:
void Widget::dealSignal()
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
//Start按鈕
void Widget::on_pushButtonStart_clicked()
{
if(thread->isRunning() == true)
{
return;
}
//啟動線程,但是沒有啟動線程處理函數
thread->start();
myT->setFlag(false);
//不能直接調用線程處理函數,直接調用導致線程處理函數和主線程在同一個線程
//myT->MyTimeout();
//只能通過 signal - slot方式
emit startThread();
}
//Stop按鈕
void Widget::on_pushButtonStop_clicked()
{
if(thread->isRunning() == false)
{
return;
}
myT->setFlag(true);
thread->quit();
thread->wait();
}
void Widget::dealClose()
{
on_pushButtonStop_clicked();
delete myT;
}
dealSignal中,使Lcd數字遞增。
當按下Start按鈕,啟動線程,設置線程標志為flase,通過發出startThread()信號來調用真正的線程函數MyThread::MyTimeout。
當按下Stop按鈕時,設置線程標志為true,關閉線程。
由於未給myT指定父對象,所以需要我們手動來釋放內存,當點擊x時,關閉線程,delete釋放。
運行如圖: