Qt線程QThread開啓和安全退出,QMutex線程加鎖

1、線程開啓

Qt中,開啓子線程,一般有兩種方法:

a, 定義工作類worker:

worker繼承 QThread, 重寫run函數,在主線程中實例化worker,把耗時工作放進worker的run函數中完成,結束後,往主線程中發信號,傳遞參數即可。
注意:此worker的實例,只有run函數在子線程中執行,worker的其他函數,均在主線程中執行。
如果子線程已經start開啓,run函數尚未運行完時,再次start,此時子線程不會有任何操作,run函數不會被重新調用,會繼續執行run函數。

b, 定義工作類worker:

worker繼承Qobject,在worker中完成耗時操作,並在主線程中 #include "worker.h"進來,隨後,在主線程中New出幾個子線程QThread,使用moveToThread()函數,把worker轉移進入子線程,例如:

pWorker = new Worker;
pThread1 = new QThread;
pWorker->moveToThread(pThread1)

之後,再根據需求,看何時開啓子線程:pThread1->start();
如果線程已經運行,你重複調用start其實是不會進行任何處理。

2、線程關閉

對於上面a類,在run中開啓的子線程,如果run中沒有調用exec(),使用quit(),exit(),是無法跳出run中的循環,終止子線程的。不會發生任何效果,QThread不會因爲你調用quit()函數而退出正在運行到一半的run。
但使用QThread的terminate()方法,可以立刻結束子線程,但這個函數存在非常不安定因素,不推薦使用。那麼如何安全的終止一個線程呢?

最簡單的方法是添加一個bool變量,通過主線程修改這個bool變量來進行終止,但這樣有可能引起訪問衝突,需要對其進行加鎖。

void myThread::run()
{
    int count = 0;
    m_isCanRun = true;//標記可以運行
    QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__)
    .arg((unsigned int)QThread::currentThreadId());
    emit message(str);
    while(1)
    {
        sleep(1);
        ++count;    
        doSomething();
        if(m_runCount == count)
        {
            break;
        }
 
        {
            QMutexLocker locker(&m_lock);// 此處加鎖,防止訪問衝突
            if(!m_isCanRun)//在每次循環判斷是否可以運行,如果不行就退出循環
            {
                return;
            }
        }
    }
}

因此在子線程的run函數的循環中遇到m_isCanRun的判斷後就會退出run函數,繼承QThread的函數在運行完run函數後就視爲線程完成,會發射finish信號。

子線程指針,儘量不要去delete ,這樣不安全。一般會綁定QObject::deleteLater()方法。

connect(pThread,&QThread::finished ,thread,&QObject::deleteLater);

線程結束後調用deleteLater來銷燬分配的內存。

對於上面b類,在Qt4.8之後,Qt多線程的寫法最好還是通過QObject來實現,和線程的交互通過信號和槽(實際上其實是通過事件)聯繫。

繼承QObject多線程的方法線程的創建很簡單,只要讓QThread的start函數運行起來就行,但是需要注意銷燬線程的方法: 在線程創建之後,這個QObject的銷燬不應該在主線程裏進行,而是通過deleteLater槽進行安全的銷燬,因此,繼承QObject多線程的方法在創建時有幾個槽函數需要特別關注:

  • 一個是QThread的finished信號對接QObject的deleteLater使得線程結束後,繼承QObject的那個多線程類會自己銷燬
  • 另一個是QThread的finished信號對接QThread自己的deleteLater,這個不是必須,下面官方例子就沒這樣做:
class Worker : public QObject
{
	Q_OBJECT
	public slots:
	void doWork(const QString &parameter) {
	QString result;
	/* ... here is the expensive or blocking operation ... */
	emit resultReady(result);
	}
signals:
	void resultReady(const QString &result);
};

class Controller : public QObject
{
	Q_OBJECT
	QThread workerThread;
public:
	Controller() {
	Worker *worker = new Worker;
	worker->moveToThread(&workerThread);
	connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
	connect(this, &Controller::operate, worker, &Worker::doWork);
	connect(worker, &Worker::resultReady, this, &Controller::handleResults);
	workerThread.start();
	}
	~Controller() {
	workerThread.quit();
	workerThread.wait();
	}
public slots:
	void handleResults(const QString &);
signals:
	void operate(const QString &);
};

使用QObject創建多線程的方法如下:

  • 寫一個繼承QObject的類,對需要進行復雜耗時邏輯的入口函數聲明爲槽函數
  • 此類在舊線程new出來,不能給它設置任何父對象
  • 同時聲明一個QThread對象,在官方例子裏,QThread並沒有new出來,這樣在析構時就需要調用- – QThread::wait(),如果是堆分配的話, 可以通過deleteLater來讓線程自殺
  • 把obj通過moveToThread方法轉移到新線程中,此時object已經是在線程中了
  • 把線程的finished信號和object的deleteLater槽連接,這個信號槽必須連接,否則會內存泄漏
  • 正常連接其他信號和槽(在連接信號槽之前調用moveToThread,不需要處理connect的第五個參數,否則就顯示聲明用Qt::QueuedConnection來連接)
  • 初始化完後調用’QThread::start()'來啓動線程
  • 在邏輯結束後,調用QThread::quit退出線程的事件循環

3、QMutex線程加鎖

QMutex類提供的是多線程之間的訪問順序化。QMutex的目的是保護一個對象、函數、數據結構或者代碼段,所以同一時間只有一個線程可以訪問它。
例如:

void myTest::testOut()
{
	//mtex.lock();
	_sleep(3000);
	qDebug() << "this is mytest1: -----" ;	
	qDebug() << "this is mytest2:----";
	//mtex.unlock();
}

如果同時在兩個線程中調用這個方法,結果的順序將是:

this is mytest1: -----
this is mytest1: -----
this is mytest2: -----
this is mytest2: -----

如果你使用互斥量,加了鎖之後:

void myTest::testOut()
{
	mtex.lock();
	_sleep(3000);
	qDebug() << "this is mytest1: -----" ;	
	qDebug() << "this is mytest2:----";
	mtex.unlock();
}

輸出結果爲:

this is mytest1: -----
this is mytest2: -----
// 中間延遲 3000ms
this is mytest1: -----
this is mytest2: -----

一個 使用run(),moveToThread()新建子線程並結束線程,QMutex以及單例化實現的Demo示例:

myMutex1::myMutex1(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	t1 = new myThread();
	t2 = new myThread();
	t3 = new myThread();


	t4 = new QThread();
	t5 = new QThread();
	t6 = new QThread();

	my1 = myTest::getInstance();
	my2 = myTest::getInstance();
	my3 = myTest::getInstance();

	my1->moveToThread(t4);
	my2->moveToThread(t5);
	my3->moveToThread(t6);

	qDebug() << "t1 addr----" << t1;
	qDebug() << "t2 addr----" << t2;
	qDebug() << "t3 addr----" << t3;

	qDebug() << "t4 addr----" << t4;
	qDebug() << "t5 addr----" << t5;
	qDebug() << "t6 addr----" << t6;
}

在這裏插入圖片描述
該樣例Demo下載地址:
https://download.csdn.net/download/birenxiaofeigg/11963711

發佈了61 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章