66.QT-線程並發、QTcpServer並發、QThreadPool線程池


1.線程並發
一個程序內部能擁有多個線程並行執行。一個線程的執行可以被認為是一個CPU在執行該程序。
當一個程序運行在多線程下,就好像有多個CPU在同時執行該程序。
總之,多線程即可以這么理解:多線程是處理高並發的一種編程方法,即並發需要用多線程實現。

2.如何分配線程數量
利用 CPU 核心數,應用並發編程來提高效率.線程IO時間所占比例越高,需要越多線程;線程CPU時間所占比例越高,需要越少線程。
理論上:

線程數量 = CPU 核數(邏輯)+ 1 

為什么+1,《Java並發編程實戰》這么說:

  • 計算(CPU)密集型的線程恰好在某時因為發生一個頁錯誤或者因其他原因而暫停,剛好有一個“額外”的線程,可以確保在這種情況下CPU周期不會中斷工作。

IO時間和CPU時間

  • IO操作實際就是不需要CPU介入,比如DMA請求,比如把內容從硬盤上讀到內存的過程,或者是從網絡上接收信息到本機內存的過程(sleep也可以算IO操作)
  • CPU操作實際就是進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。

所以對於單核CPU而言:

最佳線程數 = 1 + (IO操作耗時/CPU操作耗時)

比如: IO操作耗時為1500ms、CPU操作耗時為500ms

最佳線程數 = 1 + (IO操作耗時/CPU操作耗時) = 1 + (1500/500) = 4

對於多核CPU而言:

最佳線程數 = CPU核心數 * (1 + (IO操作耗時/CPU操作耗時))

 

3.QTcpServer並發
QTcpServer要實現並發,首先需要子類化QTcpServer,然后重寫incomingConnection()函數.該函數定義如下所示:

[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor)
// 當有新連接時,首先會調用該函數,通過socketDescriptor參數(連接本機的套接字)創建一個QTcpSocket,設置套接字描述符,然后將QTcpSocket存儲在一個內部掛起連接列表中。最后觸發newConnection()。

我們重寫該函數,通過一個QThread將socketDescriptor參數傳到一個線程中,然后調用socketDescriptor()函數初始化一個QTcpSocket.從而達到QThread中生成一個新的QTcpSocket.

MyServer重寫如下所示:

void MyServer::incomingConnection(qintptr socketDescriptor)
{
  MyThread *thread = new MyThread(socketDescriptor, this);
  connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
  thread->start();
}

MyThread重寫run如下所示:

void MyThread::run()
{
  QTcpSocket tcpSocket;
  // 初始化一個QTcpSocket
  if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
    emit error(tcpSocket.error());
    return;
  }
  // 發送字符串
  tcpSocket.write("123456".toLocal8Bit());
  tcpSocket.disconnectFromHost();
  tcpSocket.waitForDisconnected();
}

然后在widget中:

server.listen(QHostAddress::AnyIPv4,8080);

每當一個client連接該server時,就會接收到"123456",然后被斷開.

 

4.線程池概念
假如服務器突然來了500個任務,但是我們最佳線程數是20個,不可能立馬創建500個線程,因為線程過多會帶來調度開銷,進而影響緩存局部性和整體性能。
所以我們需要線程池,線程池不僅能夠保證內核的充分利用,還能防止過分調度。
線程池就相當於排隊去銀行辦理業務.排隊的人就是要處理業務的任務線程,客服就是線程池中容納辦理業務的最大數量.每當一個辦理業務的線程結束后,線程池就會從等待隊列中取出一個線程進行業務辦理.

 

5.QThreadPool並發線程池
在Qt中,線程池可以使用QThreadPool類,用來管理多個QThread的集合.
QThreadPool管理和回收單獨的QThread對象,以幫助減少使用線程的程序中創建線程的成本。
每個Qt應用程序都有一個全局QThreadPool對象,可以通過調用globalInstance()來訪問(也可以自己定義個QThreadPool)
要使用一個QThreadPool線程,需要子類化QRunnable.並實現run()虛函數。
然后創建一個子類化QRunnable類的一個對象,並將其傳遞給QThreadPool::start(),來啟動一個線程.start()函數如下所示:

void QThreadPool::start(QRunnable *runnable, int priority = 0)
// 啟動一個runnable,如果當前線程池數量超過了maxThreadCount(),那么將runnable添加到等待隊列中.
// priority參數可用於控制runnable在等待隊列中的被執行的順序。
// 默認runnable->autoDelete()返回true,線程池將獲得可運行對象的所有權,並且在runnable->run()返回后,可運行對象將被線程池自動刪除。
// 可以通過QRunnable::setAutoDelete()來更改自動刪除標志 

QThreadPool支持通過在QRunnable::run()中調用tryStart(this)來多次執行同一個QRunnable。
如果autoDelete被啟用,QRunnable將在最后一個線程退出run函數時被刪除。
當autoDelete啟用時,使用相同的QRunnable多次調用start()會創建一個競爭條件,不建議這樣做。

在一定時間內未使用的線程將過期。默認超時時間為30000毫秒(30秒)。這可以使用setExpiryTimeout(int)來更改。設置負數將禁用過期機制。

調用maxThreadCount()查詢要使用的最大線程數。也可以使用setMaxThreadCount()來更改這個限制。默認值是QThread::idealThreadCount(). 該函數定義如下所示:

[static] int QThread::idealThreadCount()
//返回系統上可以運行的理想線程數。這是通過查詢系統中真實的和邏輯的處理器核的數量來完成的。
//如果無法檢測到處理器核數,則該函數返回1。

示例如下所示:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QRunnable>
#include <QThreadPool>

class ComputeTask : public QRunnable
{
  int index;
  void run() override
  {
      const int work = 1000 * 1000 * 40; // 每個任務計數40000000次 volatile int v = 0;
      for (int j = 0; j < work; ++j)
          ++v;
      qDebug() << index << " thread: " << QThread::currentThreadId();
  }

public:
  ComputeTask(int i) {
        index = i;
  }

};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    const int cnt = 200;   // 200個任務

    QThreadPool pool;
    qDebug() << "maxThreadCount: " << pool.maxThreadCount();
    for (int i = 0; i < cnt; ++i) {
        ComputeTask *compute = new ComputeTask(i);
        pool.start(compute);
    }

    return a.exec();
}

 打印如下所示:

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM