開發(fā)及實例章多線程_第1頁
開發(fā)及實例章多線程_第2頁
開發(fā)及實例章多線程_第3頁
開發(fā)及實例章多線程_第4頁
開發(fā)及實例章多線程_第5頁
已閱讀5頁,還剩41頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第12章Qt5多線程12.1多線程及簡單實例12.2多線程控制12.3多線程應用第12章Qt5多線程多線程具有以下幾點優(yōu)勢。(1)提高應用程序的響應速度。這對于開發(fā)圖形界面的程序尤為重要,當一個操作耗時很長時,整個系統(tǒng)都會等待這個操作,程序就不能響應鍵盤、鼠標、菜單等的操作,而使用多線程技術可將耗時長的操作置于一個新的線程,從而避免以上的問題。(2)使多CPU系統(tǒng)更加有效。當線程數不大于CPU數目時,操作系統(tǒng)可以調度不同的線程運行于不同的CPU上。(3)改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為獨立或半獨立的運行部分,這樣有利于代碼的理解和維護。第12章Qt5多線程多線程程序有以下幾個特點。(1)多線程程序的行為無法預期,當多次執(zhí)行上述程序時,每一次的運行結果都可能不同。(2)多線程的執(zhí)行順序無法保證,它與操作系統(tǒng)的調度策略和線程優(yōu)先級等因素有關。(3)多線程的切換可能發(fā)生在任何時刻、任何地點。(4)多線程對代碼的敏感度高,因此對代碼的細微修改都可能產生意想不到的結果。12.1

多線程及簡單實例【例】(難度一般)(CH1201)如圖12.1所示,單擊“開始”按鈕將啟動數個工作線程(工作線程數目由MAXSIZE宏決定),各個線程循環(huán)打印數字0~9,直到單擊“停止”按鈕終止所有線程為止。實現步驟如下。(1)在頭文件“threaddlg.h”中聲明用于界面顯示所需的控件,其具體代碼如下:#include<QDialog>#include<QPushButton>classThreadDlg:publicQDialog{Q_OBJECTpublic:ThreadDlg(QWidget*parent=0);~ThreadDlg();private:QPushButton*startBtn;QPushButton*stopBtn;QPushButton*quitBtn;};12.1

多線程及簡單實例(2)在源文件“threaddlg.cpp”的構造函數中,完成各個控件的初始化工作,其具體代碼如下:#include"threaddlg.h"#include<QHBoxLayout>ThreadDlg::ThreadDlg(QWidget*parent):QDialog(parent){setWindowTitle(tr("線程"));startBtn=newQPushButton(tr("開始"));stopBtn=newQPushButton(tr("停止"));quitBtn=newQPushButton(tr("退出"));QHBoxLayout*mainLayout=newQHBoxLayout(this);mainLayout->addWidget(startBtn);mainLayout->addWidget(stopBtn);mainLayout->addWidget(quitBtn);}12.1

多線程及簡單實例(3)此時運行程序,界面顯示如圖12.1所示。以上完成了界面的設計,下面的內容是具體的功能實現。(1)在頭文件“workthread.h”中,工作線程WorkThread類繼承自QThread類。重新實現run()函數。其具體代碼如下:#include<QThread>classWorkThread:publicQThread{Q_OBJECTpublic:WorkThread();protected:voidrun();};12.1

多線程及簡單實例(2)在源文件“workthread.cpp”中添加具體實現代碼如下:#include"workthread.h"#include<QtDebug>WorkThread::WorkThread(){}run()函數實際上是一個死循環(huán),它不停地打印數字0~9。為了顯示效果明顯,程序將每一個數字重復打印8次。voidWorkThread::run(){while(true){for(intn=0;n<10;n++)qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;}}12.1

多線程及簡單實例(3)在頭文件“threaddlg.h”中添加以下內容:#include"workthread.h"#defineMAXSIZE1 //MAXSIZE宏定義了線程的數目publicslots:voidslotStart(); //槽函數用于啟動線程voidslotStop(); //槽函數用于終止線程private:WorkThread*workThread[MAXSIZE]; //(a)12.1

多線程及簡單實例(4)在源文件“threaddlg.cpp”中添加以下內容。其中,在構造函數中添加如下代碼:connect(startBtn,SIGNAL(clicked()),this,SLOT(slotStart()));connect(stopBtn,SIGNAL(clicked()),this,SLOT(slotStop()));connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));槽函數slotStart(),當用戶單擊“開始”按鈕時,此函數將被調用。這里使用兩個循環(huán),目的是為了使新建的線程盡可能同時開始執(zhí)行,其具體實現代碼如下:voidThreadDlg::slotStart(){for(inti=0;i<MAXSIZE;i++){workThread[i]=newWorkThread(); //(a)}for(inti=0;i<MAXSIZE;i++){workThread[i]->start(); //(b)}startBtn->setEnabled(false);stopBtn->setEnabled(true);}12.1

多線程及簡單實例槽函數slotStop(),當用戶單擊“停止”按鈕時,此函數將被調用。其具體實現代碼如下:voidThreadDlg::slotStop(){for(inti=0;i<MAXSIZE;i++){workThread[i]->terminate();workThread[i]->wait();}startBtn->setEnabled(true);stopBtn->setEnabled(false);}12.1

多線程及簡單實例(5)運行結果如圖12.2所示。

MAXSIZE=5MAXSIZE=112.2

多線程控制實現線程的互斥與同步常使用的類有QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore和QWaitCondition。下面舉一個例子來說明問題:classKey{public:Key(){key=0;}intcreatKey(){++key;returnkey;}intvalue()const{returnkey;}private:intkey;};12.2

多線程控制雖然類Key產生主鍵的函數creatKey()只有一條語句執(zhí)行修改成員變量key的值,但是C++的“++”操作符并不是原子操作,通常編譯后,它將被展開成為以下三條機器命令:

將變量值載入寄存器。

將寄存器中的值加1。

將寄存器中的值寫回主存。假設當前的key值為0,如果線程1和線程2同時將0值載入寄存器,執(zhí)行加1操作并將加1后的值寫回主存,則結果是兩個線程的執(zhí)行結果將互相覆蓋,實際上僅進行了一次加1操作,此時的key值為1。12.2.1互斥量1.QMutex類QMutex類是對互斥量的處理。它被用來保護一段臨界區(qū)代碼,即每次只允許一個線程訪問這段代碼。QMutex類的lock()函數用于鎖住互斥量。如果互斥量處于解鎖狀態(tài),則當前線程就會立即抓住并鎖定它,否則當前線程就會被阻塞,直到持有這個互斥量的線程對它解鎖。線程調用lock()函數后就會持有這個互斥量,直到調用unlock()操作為止。QMutex類還提供了一個tryLock()函數。如果互斥量已被鎖定,則立即返回。例如:classKey{public:Key(){key=0;}intcreatKey(){mutex.lock();++key;returnkey;mutex.unlock();}intvalue()const{mutex.lock();returnkey;mutex.unlock();}private:intkey;QMutexmutex;};12.2.1互斥量2.QMutexLocker類Qt提供的QMutexLocker類可以簡化互斥量的處理,它在構造函數中接收一個QMutex對象作為參數并將其鎖定,在析構函數中解鎖這個互斥量,這樣就解決了以上問題。例如:classKey{public:Key(){key=0;}intcreatKey(){QmutexLockerlocker(&mutex);++key;returnkey;}intvalue()const{QmutexLockerlocker(&mutex);returnkey;}private:intkey;QMutexmutex;};12.2.2信號量生產者/消費者實例中對同步的需求有兩處:(1)如果生產者過快地生產數據,將會覆蓋消費者還沒有讀取的數據。(2)如果消費者過快地讀取數據,將越過生產者并且讀取到一些過期數據。針對以上問題,可以有兩種解決方法:(1)首先使生產者填滿整個緩沖區(qū),然后等待消費者讀取整個緩沖區(qū),這是一種比較笨拙的方法。(2)使生產者和消費者線程同時分別操作緩沖區(qū)的不同部分,這是一種比較高效的方法。12.2.2信號量【例】(難度一般)(CH1202)基于控制臺程序實現。(1)源文件“main.cpp”中添加的具體實現代碼如下:#include<QCoreApplication>#include<QSemaphore>#include<QThread>#include<stdio.h>constintDataSize=1000;constintBufferSize=80;intbuffer[BufferSize]; //(a)QSemaphorefreeBytes(BufferSize); //(b)QSemaphoreusedBytes(0); //(c)(2)Producer類繼承自QThread類,作為生產者類,其聲明如下:classProducer:publicQThread{public:Producer();voidrun();};12.2.2信號量Producer構造函數中沒有實現任何內容:Producer::Producer(){}Producer::run()函數的具體實現代碼如下:voidProducer::run(){for(inti=0;i<DataSize;i++){freeBytes.acquire(); //(a)buffer[i%BufferSize]=(i%BufferSize); //(b)usedBytes.release(); //(c)}}12.2.2信號量(3)Consumer類繼承自QThread類,作為消費者類,其聲明如下:classConsumer:publicQThread{public:Consumer();voidrun();};Consumer構造函數中沒有實現任何內容:Consumer::Consumer(){}12.2.2信號量Consumer::run()函數的具體實現代碼如下:voidConsumer::run(){for(inti=0;i<DataSize;i++){usedBytes.acquire(); //(a)fprintf(stderr,"%d",buffer[i%BufferSize]); //(b)if(i%16==0&&i!=0)fprintf(stderr,"\n");freeBytes.release(); //(c)}fprintf(stderr,"\n");}12.2.2信號量(4)main()函數的具體內容如下:intmain(intargc,char*argv[]){QCoreApplicationa(argc,argv);Producerproducer;Consumerconsumer; /*啟動生產者和消費者線程*/producer.start();consumer.start(); /*等待生產者和消費者各自執(zhí)行完畢后自動退出*/producer.wait();consumer.wait();returna.exec();}12.2.2信號量(5)最終運行結果如圖12.3所示。12.2.3線程等待與喚醒【例】(難度一般)(CH1203)使用QWaitCondition類解決生產者和消費者問題。源文件“main.cpp”的具體內容如下:#include<QCoreApplication>#include<QWaitCondition>#include<QMutex>#include<QThread>#include<stdio.h>constintDataSize=1000;constintBufferSize=80;intbuffer[BufferSize];QWaitConditionbufferEmpty;QWaitConditionbufferFull;QMutexmutex; //(a)intnumUsedBytes=0; //(b)intrIndex=0; //(c)12.2.3線程等待與喚醒生產者線程Producer類繼承自QThread類,其聲明如下:classProducer:publicQThread{public:Producer();voidrun();};Producer構造函數無須實現:Producer::Producer(){}12.2.3線程等待與喚醒Producer::run()函數的具體內容如下:voidProducer::run(){for(inti=0;i<DataSize;i++) //(a){mutex.lock();if(numUsedBytes==BufferSize) //(b)bufferEmpty.wait(&mutex); //(c)buffer[i%BufferSize]=numUsedBytes; //(d)++numUsedBytes; //增加numUsedBytes變量bufferFull.wakeAll(); //(e)mutex.unlock();}}12.2.3線程等待與喚醒其中,(a)for(inti=0;i<DataSize;i++){mutex.lock();…mutex.unlock();}:for循環(huán)中的所有語句都需要使用互斥量加以保護,以保證其操作的原子性。(b)if(numUsedBytes==BufferSize):首先檢查緩沖區(qū)是否已經填滿。(c)bufferEmpty.wait(&mutex):如果緩沖區(qū)已經填滿,則等待“緩沖區(qū)有空位”(bufferEmpty變量)條件成立。wait()函數將互斥量解鎖并在此等待,其原型如下:boolQWaitCondition::wait( QMutex*mutex, unsignedlongtime=ULONG_MAX)(d)buffer[i%BufferSize]=numUsedBytes:如果緩沖區(qū)未被填滿,則向緩沖區(qū)中寫入一個整數值。(e)bufferFull.wakeAll():最后喚醒等待“緩沖區(qū)有可用數據”(bufferEmpty變量)條件為“真”的線程。12.2.3線程等待與喚醒消費者線程Consumer類繼承自QThread類,其聲明如下:classConsumer:publicQThread{public:Consumer();voidrun();};Consumer構造函數中無須實現內容:Consumer::Consumer(){}12.2.3線程等待與喚醒Consumer::run()函數的具體內容如下:voidConsumer::run(){forever{mutex.lock();if(numUsedBytes==0)bufferFull.wait(&mutex); //(a)printf("%ul::[%d]=%d\n",currentThreadId(),rIndex,buffer[rIndex]); //(b)rIndex=(++rIndex)%BufferSize; //將rIndex變量循環(huán)加1--numUsedBytes; //(c)bufferEmpty.wakeAll(); //(d)mutex.unlock();}printf("\n");}12.2.3線程等待與喚醒main()函數的具體內容如下:intmain(intargc,char*argv[]){QCoreApplicationa(argc,argv);Producerproducer;ConsumerconsumerA;ConsumerconsumerB;producer.start();consumerA.start();consumerB.start();producer.wait();consumerA.wait();consumerB.wait();returna.exec();}12.2.3線程等待與喚醒程序最終的運行結果如圖12.4所示。12.3

多線程應用12.3.1【實例】:服務器編程【例】(難度中等)(CH1204)服務器編程。首先,建立服務器端工程“TimeS”。文件代碼如下。(1)在頭文件“dialog.h”中,定義服務器端界面類Dialog繼承自QDialog類,其具體代碼如下:#include<QDialog>#include<QLabel>#include<QPushButton>classDialog:publicQDialog{Q_OBJECTpublic:Dialog(QWidget*parent=0);~Dialog();private:QLabel*Label1; //此標簽用于顯示監(jiān)聽端口QLabel*Label2; //此標簽用于顯示請求次數QPushButton*quitBtn; //退出按鈕};12.3.1【實例】:服務器編程(2)在源文件“dialog.cpp”中,Dialog類的構造函數完成了初始化界面,其具體代碼如下:#include"dialog.h"#include<QHBoxLayout>#include<QVBoxLayout>Dialog::Dialog(QWidget*parent):QDialog(parent){setWindowTitle(tr("多線程時間服務器"));Label1=newQLabel(tr("服務器端口:"));Label2=newQLabel;quitBtn=newQPushButton(tr("退出"));QHBoxLayout*BtnLayout=newQHBoxLayout;BtnLayout->addStretch(1);BtnLayout->addWidget(quitBtn);BtnLayout->addStretch(1);QVBoxLayout*mainLayout=newQVBoxLayout(this);mainLayout->addWidget(Label1);mainLayout->addWidget(Label2);mainLayout->addLayout(BtnLayout);connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));}12.3.1【實例】:服務器編程(3)此時運行服務器端工程“TimeS”,界面顯示如圖12.5所示。12.3.1【實例】:服務器編程(4)在服務器端工程“TimeS”中,添加C++Class文件“timethread.h”及“timethread.cpp”。在頭文件“timethread.h”中,工作線程TimeThread類繼承自QThread類,實現TCP套接字,其具體代碼如下:#include<QThread>#include<QtNetwork>#include<QTcpSocket>classTimeThread:publicQThread{Q_OBJECTpublic:TimeThread(intsocketDescriptor,QObject*parent=0);voidrun(); //重寫此虛函數signals:voiderror(QTcpSocket::SocketErrorsocketError); //出錯信號private:intsocketDescriptor; //套接字描述符};12.3.1【實例】:服務器編程(5)在源文件“timethread.cpp”中,TimeThread類的構造函數只是初始化了套接字描述符,其具體代碼如下:#include"timethread.h"#include<QDateTime>#include<QByteArray>#include<QDataStream>TimeThread::TimeThread(intsocketDescriptor,QObject*parent):QThread(parent),socketDescriptor(socketDescriptor){}12.3.1【實例】:服務器編程TimeThread::run()函數是工作線程(TimeThread)的實質所在,當在TimeServer::ingConnection()函數中調用了thread->start()函數后,此虛函數開始執(zhí)行,其具體代碼如下:voidTimeThread::run(){QTcpSockettcpSocket; //創(chuàng)建一個QTcpSocket類

if(!tcpSocket.setSocketDescriptor(socketDescriptor)) //(a){emiterror(tcpSocket.error()); //(b)return;}QByteArrayblock;QDataStreamout(&block,QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_8);uinttime2u=QDateTime::currentDateTime().toTime_t();//(c)out<<time2u;tcpSocket.write(block); //將獲得的當前時間傳回客戶端

tcpSocket.disconnectFromHost(); //斷開連接

tcpSocket.waitForDisconnected(); //等待返回}12.3.1【實例】:服務器編程(6)在服務器端工程“TimeS”中添加C++Class文件“timeserver.h”及“timeserver.cpp”。在頭文件“timeserver.h”中,實現了一個TCP服務器端,類TimeServer繼承自QTcpServer類,其具體代碼如下:#include<QTcpServer>classDialog; //服務器端的聲明classTimeServer:publicQTcpServer{Q_OBJECTpublic:TimeServer(QObject*parent=0);protected:voidingConnection(intsocketDescriptor); //(a)private:Dialog*dlg; //(b)};12.3.1【實例】:服務器編程(7)在源文件“timeserver.cpp”中,構造函數只是用傳入的父類指針parent初始化私有變量dlg,其具體代碼如下:#include"timeserver.h"#include"timethread.h"#include"dialog.h"TimeServer::TimeServer(QObject*parent):QTcpServer(parent){dlg=(Dialog*)parent;}重寫的虛函數ingConnection()的具體代碼如下:voidingConnection(intsocketDescriptor){TimeThread*thread=newTimeThread(socketDescriptor,0); //(a)connect(thread,SIGNAL(finished()),dlg,SLOT(slotShow())); //(b)connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()),Qt::DirectConnection); //(c)thread->start(); //(d)}12.3.1【實例】:服務器編程(8)在服務器端界面的頭文件“dialog.h”中添加的具體代碼如下:classTimeServer;publicslots:voidslotShow(); //此槽函數用于界面上顯示的請求次數private:TimeServer*timeServer; //TCP服務器端timeServerintcount; //請求次數計數器count12.3.1【實例】:服務器編程(9)在源文件“dialog.cpp”中,添加的頭文件如下:#include<QMessageBox>#include"timeserver.h"其中,在Dialog類的構造函數中添加的內容,用于啟動服務器端的網絡監(jiān)聽,其具體實現如下:count=0;timeServer=newTimeServer(this);if(!timeServer->listen()){QMessageBox::critical(this,tr("多線程時間服務器"),tr("無法啟動服務器:%1.").arg(timeServer->errorString()));close();return;}Label1->setText(tr("服務器端口:%1.").arg(timeServer->serverPort()));在源文件“dialog.cpp”中,槽函數slotShow()的具體內容如下:voidDialog::slotShow(){Label2->setText(tr("第%1次請求完畢。").arg(++count));}12.3.1【實例】:服務器編程(10)在服務器端工程文件“TimeS”中添加如下代碼:QT+=network(11)最后運行服務器端工程“TimeS”,結果如圖12.6所示。12.3.2【實例】:客戶端編程【例】(難度中等)(CH1205)客戶端編程。界面效果如圖12.7所示。12.3.2【實例】:客戶端編程操作步驟如下。(1)建立客戶端工程“TimeC”。在頭文件“timeclient.h”中,定義了客戶端界面類TimeClient繼承自QDialog類,其具體代碼。(2)在源文件“timeclient.cpp”中,TimeClient類的構造函數完成了初始化界面,其具體代碼。在源文件“timeclient.cpp”中,enableGetBtn()函數的具體代碼如下:voidTimeClient::enableGetBtn(){getBtn->setEnabled(!serverNameLineEdit->text().isEmpty()&&!portLineEdit->text().isEmpty());

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論