QT 线程池 + TCP 实战笔记

很久以前做过ACE + MFC/QT 的中轻量级线程池应用,大概就是利用线程池执行客户机上的运算需求,将结果返回。ACE是跨平台重量级的通信中间件,与常见的应用程序框架需要精心契合,才能不出问题。最近想到既然QT框架本身就已经具有各类功能,何不玩一玩呢,那就开搞!这个实验的代码可以从我的资源内下载。

第一步打算实现的模式,我们需要一个设置为CPU核心数的线程池,这个线程池可以异步接受N个数据生产者传入的数据,均衡的分配处理任务,处理后的数据返回给某1个或者几个消费者。有两种均衡方法。一种是生产者粒度的均衡。同一个生产者的各批数据FIFO顺序不被打破,这需要判断,当处理线程队列中还有该生产者的数据时,不改变当前处理线程。第二种是数据粒度的并行,某个生产者传来的数据被分配到不同的线程,不保证后到的数据后被处理(也可能先到的处理的慢,后到的快)。

这种异步队列机制如果在MFC、WinAPI中,需要手工使用 Mutex 同步队列,更可恶的是分配的数据对象的生存期非常微妙,一不小心就会出红叉叉。QT首先为我们提供了信号和槽的机制,且该机制原生支持跨线程。假设我们在16核心服务器上,则使用 15个 QThread对象管理15组工作线程(留一个给主界面)。但是,如果仔细看了QT的文档,就会发现QThread的信号事件循环默认是在创建者中(很多时候就是主线程!),所以,要想让槽在子线程运行,一般是派生一个QObject的类,并把对象MoveToThread到某个QThread管理的线程上去。这样,信号和槽就是全异步FIFO了。其次,QT提供了引用计数的QByteArray封装,这个东西在参数传递的时候,速度很快,很少出现memcpy,生存期也特别容易控制。虽然C++11里有 shared_ptr<T>,但是那个东西还是需要在一开始new 一个int8型的存储区,很讨厌。

说了这么多,上关键代码。

先是线程池的封装qghthreadengine.h

  1. #ifndef QGHTHREADENGINE_H  
  2. #define QGHTHREADENGINE_H  
  3.  
  4. #include <QObject>  
  5. #include <QThread>  
  6. #include <QVector>  
  7. #include <QList>  
  8. #include <QMap>  
  9. #include <QMutex>  
  10. #include "qghthreadtaskitem.h"  
  11. #include "qghthreadobject.h"  
  12.  
  13. //线程池引擎,帮助用户进行动态平衡  
  14. class QGHThreadEngine : public QObject 
  15.     Q_OBJECT 
  16. public
  17.     QGHThreadEngine(QObject *parent,QGHThreadTaskItem * pTaskItem,int nThreads = 2,bool bFIFOKeep = true); 
  18.     ~QGHThreadEngine(); 
  19. protected
  20.     QVector<QThread *> m_ThreadPool; 
  21.     QVector<QGHThreadObject *> m_ThreadObjs; 
  22.     QGHThreadTaskItem * m_pThreadTaskItem; 
  23.     int m_nThreads; 
  24.     bool m_bFIFOKeep; 
  25. private
  26.     //各个m_ThreadPool\m_ThreadObjs的任务数  
  27.     QMap<QObject *,qint32> m_map_Tasks;         
  28.     //m_bFIFOKeep == true 时,下面两个成员将保证非空闲的单个 data_source 将始终在单一线程处理  
  29.     //各个data_source 目前的处理线程  
  30.     QMap<QObject *,QObject *> m_map_busy_source_task;   
  31.     //各个data_source 目前的排队数目  
  32.     QMap<QObject *,int> m_map_busy_source_counter;         
  33. public
  34.     void SetThreadTaskItem(QGHThreadTaskItem * pTaskItem); 
  35.     QList<qint32> CurrentLoad() 
  36.     { 
  37.         return m_map_Tasks.values(); 
  38.     } 
  39. public slots: 
  40.     void append_new(QObject * data_source, const QByteArray & data); 
  41.     //捕获QGHThreadObject::sig_process_finished, 以便管理data_source的 FIFO 顺序  
  42.     void on_sig_process_finished(QObject * data_source); 
  43. signals: 
  44.     //************************************  
  45.     // Method:    do_task  
  46.     // FullName:  QGHThreadEngine::do_task  
  47.     // Access:    public   
  48.     // Returns:   void  
  49.     // Qualifier:  
  50.     // Parameter: QObject *     任务来源 (相同任务源的任务,在队列非空时会被安排到同一个线程处理,以确保对相同源的FIFO)  
  51.     // Parameter: QByteArray    任务体   
  52.     // Parameter: QObject *     处理任务的线程对象(QGHThreadObject)  
  53.     //************************************  
  54.     void do_task(QObject *, const QByteArray &,QObject *); 
  55. }; 
  56.  
  57. #endif // QGHTHREADENGINE_H 

实现qghthreadengine.cpp:

  1. #include "qghthreadengine.h"  
  2. #include <assert.h>  
  3. QGHThreadEngine::QGHThreadEngine(QObject *parent,QGHThreadTaskItem * pTaskItem,int nThreads,bool bFIFOKeep) 
  4.     : QObject(parent), 
  5.     m_nThreads(nThreads), 
  6.     m_pThreadTaskItem(pTaskItem), 
  7.     m_bFIFOKeep(bFIFOKeep) 
  8.     assert(nThreads>0 && nThreads<512 && pTaskItem!=NULL); 
  9.     //创建固定数目的线程  
  10.     for (int i=0;i<nThreads;i++) 
  11.     { 
  12.         QThread * pNewThread = new QThread(this); 
  13.         QGHThreadObject * pNewObject = new QGHThreadObject(0,pTaskItem); 
  14.         //记录下来  
  15.         m_ThreadPool.push_back(pNewThread); 
  16.         m_ThreadObjs.push_back(pNewObject); 
  17.         m_map_Tasks[pNewObject] = 0; 
  18.         pNewThread->start(); 
  19.         //把QGHThreadObject的信号、曹处理搬移到子线程内  
  20.         pNewObject->moveToThread(pNewThread); 
  21.         //连接处理完成消息  
  22.         connect(pNewObject,SIGNAL(sig_process_finished(QObject *)),this,SLOT(on_sig_process_finished(QObject *))); 
  23.         //连接处理新任务消息  
  24.         connect(this,SIGNAL(do_task(QObject *, const QByteArray &,QObject *)),pNewObject,SLOT(process(QObject *, const QByteArray &,QObject *))); 
  25.  
  26.     } 
  27.  
  28. QGHThreadEngine::~QGHThreadEngine() 
  29.     foreach(QGHThreadObject * obj,m_ThreadObjs) 
  30.     { 
  31.         disconnect(obj,SIGNAL(sig_process_finished(QObject *)),this,SLOT(on_sig_process_finished(QObject *))); 
  32.         obj->deleteLater(); 
  33.     } 
  34.     foreach(QThread * th ,m_ThreadPool) 
  35.     { 
  36.         disconnect(this,SIGNAL(do_task(QObject *, QByteArray,QObject *)),th,SLOT(process(QObject *, QByteArray,QObject *))); 
  37.         th->exit(0); 
  38.         th->wait(); 
  39.     } 
  40.  
  41. //负载均衡添加任务,生产者的信号要挂接到这个槽上  
  42. void QGHThreadEngine::append_new(QObject * data_source, const QByteArray &  data) 
  43.     QObject * pMinObj = 0; 
  44.     //对一批来自同一数据源的数据,使用同样的数据源处理,以免发生多线程扰乱FIFO对单个data_source的完整性  
  45.     if (m_map_busy_source_counter.find(data_source)!=m_map_busy_source_counter.end()&& m_bFIFOKeep==true
  46.     { 
  47.         m_map_busy_source_counter[data_source]++; 
  48.         pMinObj = m_map_busy_source_task[data_source]; 
  49.     } 
  50.     else 
  51.     { 
  52.         qint32 nMinCost = 0x7fffffff; 
  53.         //寻找现在最空闲的一个线程  
  54.         for (QMap<QObject *,qint32>::iterator p = m_map_Tasks.begin();p!=m_map_Tasks.end();p++) 
  55.         { 
  56.             if (p.value()< nMinCost) 
  57.             { 
  58.                 nMinCost = p.value(); 
  59.                 pMinObj = p.key(); 
  60.             } 
  61.         } 
  62.         if (pMinObj) 
  63.         { 
  64.             m_map_busy_source_counter[data_source] = 1; 
  65.             m_map_busy_source_task[data_source] = pMinObj; 
  66.         } 
  67.     } 
  68.     if (pMinObj) 
  69.     { 
  70.         m_map_Tasks[pMinObj]++; 
  71.         emit do_task(data_source,data,pMinObj); 
  72.     } 
  73. void QGHThreadEngine::on_sig_process_finished(QObject * data_source) 
  74.     if (m_map_Tasks.find(sender())!=m_map_Tasks.end()) 
  75.     { 
  76.         m_map_Tasks[sender()]--; 
  77.     } 
  78.     if (m_map_busy_source_counter.find(data_source)!=m_map_busy_source_counter.end()) 
  79.     { 
  80.         m_map_busy_source_counter[data_source]--; 
  81.         if (m_map_busy_source_counter[data_source]<=0) 
  82.         { 
  83.             m_map_busy_source_counter.remove(data_source); 
  84.             m_map_busy_source_task.remove(data_source); 
  85.         } 
  86.     } 
  87. }     

用于绑定的 qghthreadobject.h

  1. #ifndef QGHTHREADOBJECT_H  
  2. #define QGHTHREADOBJECT_H  
  3. #include <QObject>  
  4. #include "qghthreadtaskitem.h"  
  5. //用于在子线程内具体承担事件循环的类,用户无需重载  
  6. class QGHThreadObject:public QObject 
  7.     Q_OBJECT 
  8.  
  9. public
  10.     QGHThreadObject(QObject *parent,QGHThreadTaskItem * pThreadTaskItem); 
  11.     ~QGHThreadObject(); 
  12. public
  13.     void SetThreadTaskItem(QGHThreadTaskItem * pThreadTaskItem); 
  14. public slots: 
  15.     //************************************  
  16.     // Method:    process  
  17.     // FullName:  QGHThreadObject::process  
  18.     // Access:    public   
  19.     // Returns:   void  
  20.     // Qualifier:  
  21.     // Parameter: QObject *     任务来源 (相同任务源的任务,在队列非空时会被安排到同一个线程处理,以确保对相同源的FIFO)  
  22.     // Parameter: QByteArray    任务体   
  23.     // Parameter: QObject *     处理任务的线程对象(QGHThreadObject)  
  24.     //************************************  
  25.     void process(QObject * data_source, const QByteArray &data,QObject * target); 
  26. private
  27.     QGHThreadTaskItem * m_pThreadTaskItem; 
  28. signals: 
  29.     //信号,表示一次处理已经完成。QGHThreadEngine捕获该信号,管理data_source的 FIFO 顺序  
  30.     void sig_process_finished(QObject * data_source); 
  31. }; 
  32. #endif 

相应实现qghthreadobject.cpp

  1. #include "qghthreadobject.h"  
  2. #include <assert.h>  
  3.  
  4. QGHThreadObject::QGHThreadObject(QObject *parent,QGHThreadTaskItem * pThreadTaskItem) 
  5.     : QObject(parent), 
  6.     m_pThreadTaskItem(pThreadTaskItem) 
  7.     assert(pThreadTaskItem!=NULL); 
  8.  
  9.  
  10. QGHThreadObject::~QGHThreadObject() 
  11. void QGHThreadObject::process(QObject * data_source, const QByteArray &data,QObject * target) 
  12.     if (target==this
  13.     { 
  14.         m_pThreadTaskItem->run(data_source,data); 
  15.         emit sig_process_finished(data_source); 
  16.     } 
  17.  
  18. void QGHThreadObject::SetThreadTaskItem(QGHThreadTaskItem * pThreadTaskItem) 
  19.     assert(pThreadTaskItem!=NULL); 
  20.     m_pThreadTaskItem = pThreadTaskItem; 

最后,是供用户重载的实际处理方法的纯虚基类qghthreadtaskitem.h

  1. #ifndef QGHTHREADTASKITEM_H  
  2. #define QGHTHREADTASKITEM_H  
  3. #include <QObject>  
  4. //用户重载该类,实现自定义方法的线程池调用  
  5. class QGHThreadTaskItem:public QObject 
  6.     Q_OBJECT 
  7.  
  8. public
  9.     QGHThreadTaskItem(QObject *parent); 
  10.     ~QGHThreadTaskItem(); 
  11. public
  12.     virtual void run(QObject * task_source, const QByteArray & data_array) = 0; 
  13.  
  14. }; 

  1. #endif 





有了线程池,我们下一步就利用 QTcpServer 搭建一个服务器,接受客户端的连接,并把数据发送到线程池上。由于 QTcpServer 资料太多了,这里不在赘述。唯一值得注意的是,当客户端退出时,如果线程池队列中还有该客户的信息,这个信息还会被处理,只是无法再发送回去而已。其实,还可实现成客户端退出,就发一个信号到线程池,删除自己的所有任务。这个也很简单,但之所以没有做,因为这些数据的处理结果可能还会被其他消费者(而非生产者自己)使用,最典型的例子是从工业传感器上采集的数据,其生成的图像需要存储到设备中去。

QTcpSocket的 Write 方法默认是支持大体积数据的,即使一次发了500MB的数据,只要硬件资源可以承受,调用也会成功并立刻返回。接受者会以一定的载荷大小不停的触发readyRead,直到发送全部成功。但是,为了能够观察到并控制收发队列中的数据包的大小、体积,我们在外层实现了一个发送队列,每次以 payLoad为大小发送数据包。这是从MFC中带来的习惯,很难说好坏。

qghtcpserver.h

  1. #ifndef QGHTCPSERVER_H  
  2. #define QGHTCPSERVER_H  
  3.  
  4. #include <QTcpServer>  
  5. #include <QMap>  
  6. #include <QList>  
  7.  
  8. class QGHTcpServer : public QTcpServer 
  9.     Q_OBJECT 
  10.  
  11. public
  12.     QGHTcpServer(QObject *parent,int nPayLoad = 4096); 
  13.     ~QGHTcpServer(); 
  14.     //踢出所有客户  
  15.     void KickAllClients(); 
  16.     QList <QObject *> clientsList(); 
  17.     void SetPayload(int nPayload); 
  18. private
  19.     QMap<QObject *,QList<QByteArray> > m_buffer_sending; 
  20.     QMap<QObject *,QList<qint64> > m_buffer_sending_offset; 
  21.     QMap<QObject*,int> m_clientList; 
  22.     int m_nPayLoad; 
  23. public slots: 
  24.     //新的客户连接到来  
  25.     void new_client_recieved(); 
  26.     //客户连接被关闭  
  27.     void client_closed(); 
  28.     //新的数据到来  
  29.     void new_data_recieved(); 
  30.     //一批数据发送完毕  
  31.     void some_data_sended(qint64); 
  32.     //客户端错误  
  33.     void displayError(QAbstractSocket::SocketError socketError); 
  34.     //向客户端发送数据  
  35.     void SendDataToClient(QObject * objClient,const QByteArray &  dtarray); 
  36.     //向客户端广播数据,不包括 objFromClient  
  37.     void BroadcastData(QObject * objFromClient,const QByteArray &  dtarray); 
  38. signals: 
  39.     //错误信息  
  40.     void evt_SocketError(QObject * senderSock ,QAbstractSocket::SocketError socketError); 
  41.     //新的客户端连接  
  42.     void evt_NewClientConnected(QObject * client); 
  43.     //客户端退出  
  44.     void evt_ClientDisconnected(QObject * client); 
  45.     //收到一批数据  
  46.     void evt_Data_recieved(QObject * ,const QByteArray &  ); 
  47.     //一批数据被发送  
  48.     void evt_Data_transferred(QObject * client,qint64); 
  49. }; 
  50.  
  51. #endif // QGHTCPSERVER_H 

qghtcpserver.cpp

  1. #include "qghtcpserver.h"  
  2. #include <assert.h>  
  3. #include <QTcpSocket>  
  4. QGHTcpServer::QGHTcpServer(QObject *parent,int nPayLoad ) 
  5.     : QTcpServer(parent), 
  6.     m_nPayLoad(nPayLoad) 
  7.     assert(m_nPayLoad>=256 && m_nPayLoad<=16*1024*1024); 
  8.     connect(this, SIGNAL(newConnection()), this, SLOT(new_client_recieved())); 
  9.  
  10. QGHTcpServer::~QGHTcpServer() 
  11.  
  12. QList <QObject *> QGHTcpServer::clientsList() 
  13.     return m_clientList.keys(); 
  14. void QGHTcpServer::SetPayload(int nPayload) 
  15.     m_nPayLoad = nPayload; 
  16.     assert(m_nPayLoad>=256 && m_nPayLoad<=16*1024*1024); 
  17.  
  18. void QGHTcpServer::new_client_recieved() 
  19.     QTcpSocket * sock_client = nextPendingConnection(); 
  20.     while (sock_client) 
  21.     { 
  22.         connect(sock_client, SIGNAL(readyRead()),this, SLOT(new_data_recieved())); 
  23.         connect(sock_client, SIGNAL(disconnected()),this,SLOT(client_closed())); 
  24.         connect(sock_client, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(displayError(QAbstractSocket::SocketError))); 
  25.         connect(sock_client, SIGNAL(bytesWritten(qint64)), this, SLOT(some_data_sended(qint64))); 
  26.         m_clientList[sock_client] = 0; 
  27.         emit evt_NewClientConnected(sock_client); 
  28.         sock_client = nextPendingConnection(); 
  29.     } 
  30. void QGHTcpServer::client_closed() 
  31.     QTcpSocket * pSock = qobject_cast<QTcpSocket*>(sender()); 
  32.     if (pSock) 
  33.     { 
  34.         emit evt_ClientDisconnected(pSock); 
  35.         m_buffer_sending.remove(pSock); 
  36.         m_buffer_sending_offset.remove(pSock); 
  37.         m_clientList.remove(pSock); 
  38.         pSock->deleteLater(); 
  39.     } 
  40. void QGHTcpServer::new_data_recieved() 
  41.     QTcpSocket * pSock = qobject_cast<QTcpSocket*>(sender()); 
  42.     if (pSock) 
  43.         emit evt_Data_recieved(pSock,pSock->readAll()); 
  44. void QGHTcpServer::some_data_sended(qint64 wsended) 
  45.     QTcpSocket * pSock = qobject_cast<QTcpSocket*>(sender()); 
  46.     if (pSock) 
  47.     { 
  48.         emit evt_Data_transferred(pSock,wsended); 
  49.         QList<QByteArray> & list_sock_data = m_buffer_sending[pSock]; 
  50.         QList<qint64> & list_offset = m_buffer_sending_offset[pSock]; 
  51.         while (list_sock_data.empty()==false
  52.         { 
  53.             QByteArray & arraySending = *list_sock_data.begin(); 
  54.             qint64 & currentOffset = *list_offset.begin(); 
  55.             qint64 nTotalBytes = arraySending.size(); 
  56.             assert(nTotalBytes>=currentOffset); 
  57.             qint64 nBytesWritten = pSock->write(arraySending.constData()+currentOffset,qMin((int)(nTotalBytes-currentOffset),m_nPayLoad)); 
  58.             currentOffset += nBytesWritten; 
  59.             if (currentOffset>=nTotalBytes) 
  60.             { 
  61.                 list_offset.pop_front(); 
  62.                 list_sock_data.pop_front(); 
  63.             } 
  64.             else 
  65.                 break
  66.         } 
  67.     } 
  68. void QGHTcpServer::displayError(QAbstractSocket::SocketError socketError) 
  69.     QTcpSocket * pSock = qobject_cast<QTcpSocket*>(sender()); 
  70.     if (pSock) 
  71.     { 
  72.         emit evt_SocketError(pSock,socketError); 
  73.         pSock->disconnectFromHost(); 
  74.     } 
  75.  
  76. void QGHTcpServer::SendDataToClient(QObject * objClient,const QByteArray &  dtarray) 
  77.     if (m_clientList.find(objClient)==m_clientList.end()) 
  78.         return
  79.     QTcpSocket * pSock = qobject_cast<QTcpSocket*>(objClient); 
  80.     if (pSock&&dtarray.size()) 
  81.     { 
  82.         QList<QByteArray> & list_sock_data = m_buffer_sending[pSock]; 
  83.         QList<qint64> & list_offset = m_buffer_sending_offset[pSock]; 
  84.         if (list_sock_data.empty()==true
  85.         { 
  86.             qint64 bytesWritten = pSock->write(dtarray.constData(),qMin(dtarray.size(),m_nPayLoad)); 
  87.             if (bytesWritten < dtarray.size()) 
  88.             { 
  89.                 list_sock_data.push_back(dtarray); 
  90.                 list_offset.push_back(bytesWritten); 
  91.             } 
  92.         } 
  93.         else 
  94.         { 
  95.             list_sock_data.push_back(dtarray); 
  96.             list_offset.push_back(0); 
  97.         }             
  98.     }     
  99. void QGHTcpServer::BroadcastData(QObject * objClient,const QByteArray &  dtarray) 
  100.     for(QMap<QObject *,int>::iterator p = m_clientList.begin();p!=m_clientList.end();p++) 
  101.     { 
  102.         QTcpSocket * pSock = qobject_cast<QTcpSocket*>(p.key()); 
  103.         if (pSock&&dtarray.size()&&pSock!=objClient) 
  104.         { 
  105.             QList<QByteArray> & list_sock_data = m_buffer_sending[pSock]; 
  106.             QList<qint64> & list_offset = m_buffer_sending_offset[pSock]; 
  107.             if (list_sock_data.empty()==true
  108.             { 
  109.                 qint64 bytesWritten = pSock->write(dtarray.constData(),qMin(dtarray.size(),m_nPayLoad)); 
  110.                 if (bytesWritten < dtarray.size()) 
  111.                 { 
  112.                     list_sock_data.push_back(dtarray); 
  113.                     list_offset.push_back(bytesWritten); 
  114.                 } 
  115.                 else 
  116.                 { 
  117.                     list_sock_data.push_back(dtarray); 
  118.                     list_offset.push_back(0); 
  119.                 }             
  120.             } 
  121.         }     
  122.     } 
  123. void QGHTcpServer::KickAllClients() 
  124.     QList<QObject *> clientList = m_clientList.keys(); 
  125.     foreach(QObject * obj,clientList) 
  126.     { 
  127.         QTcpSocket * pSock = qobject_cast<QTcpSocket*>(obj); 
  128.         if (pSock) 
  129.         { 
  130.             pSock->disconnectFromHost(); 
  131.         }     
  132.     } 
  133.  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值