在我接触多线程编程以来,都是把“多线程”等同于“异步”,使用多线程基本上也都是为了不阻塞主线程(如界面),才单独开一个线程“后台”运行。最近遇到的情况是数据分析程序的处理速度跟不上数据采集程序,因此考虑使用多个worker线程并行的处理采集到的数据。尝试使用OpenMP
,在程序中使用类似于这种代码
#pragma omp parallel for
for (int i=0;i<6;++i){run();}
但是性能还是达不到期望,电脑有10多核,开6个线程,却只能把运行速度提高2~3倍,并且再增加线程也没有提高速度。
You’re doing it wrong…
决定好好琢磨一下多线程。我这里主程序是用Qt做的,希望尽量使用Qt自带的库,所以多线程也是使用QThread
,仔细考虑过之后得到的思路是维护一个线程安全的队列,然后数据采集线程和多个数据处理线程分别向这个队列传入和取出数据,实现并发,也就是所谓的“单生产者、多消费者模式”。
线程安全的队列是这样的
//dataqueue.h
#include <QQueue>
#include <QMutexLocker>
//thread-safe queue
class DataQueue
{
QMutex m_mutex;QQueue<void*> m_datas;
public:
DataQueue();
void enqueue(void*);void* dequeue();
};
//dataqueue.cpp
#include "dataqueue.h"
void DataQueue::enqueue(void *data){
QMutexLocker locker(&m_mutex);
m_datas.enqueue(data);
}
void* DataQueue::dequeue(){
QMutexLocker locker(&m_mutex);
return m_datas.empty()?NULL:m_datas.dequeue();
}
基本上就是封装了一下QQueue
,数据用空指针void*
表示。
数据采集线程就直接实例化一个DataQueue
然后调用enqueue
方法即可,数据分析线程部分需要仔细考虑,主要的问题就是当有新数据到来时(加入DataQueue
之后),分析线程是竞争性地去获取数据还是被动地被添加数据到线程单独的数据队列中,以及当没有数据时线程(以下所说的“线程”都是数据分析线程)是wait
直到被唤醒还是sleep
一段时间再次尝试从DataQueue
取数据。
这里只考虑下面两种情况:
线程被动获取数据并被唤醒
这种方法需要维护一个“线程池”,作为数据采集线程的成员变量,并且每个数据分析线程都要有一个单独的DataQueue
数据队列作为成员变量。
当有新数据到来时,要将数据加入某个线程的数据队列,这个线程的选取有多种方法,可以遍历所有的线程,找到数据队列中数据最少的一个线程,还可以随机地抽出一个线程。总之是要让各个线程的负载比较均匀。
当线程的数据队列为空时,使用QWaitCondition
使线程wait
,然后当有新数据加入进来时,再wakeOne
。
这种方法的优点是不会出现太多的锁竞争,因为不会出现多个线程竞争获取数据的情况,但问题是需要主动维护各个线程的负载均衡,比较麻烦。
线程主动轮询获取数据
这种方法比较简单粗暴,也是我最终选择的方法。全局地维护一个DataQueue
数据队列,所有线程(数据采集和分析)都是独立运行的,采集线程往队列里加数据,分析线程从队列里取数据,当队列里没有数据时,分析线程会sleep
一段时间,然后再从队列里取数据。数据分析线程的实现大致是这样的
void DataProcessor::run(){
setRunningFlag(true);
void *buffer=NULL;
while(getRunningFlag()){
buffer=g_dataQueue.dequeue();
if(NULL==buffer){msleep(5);continue;}
processData(buffer);
free(buffer);
}
}
bool DataProcessor::getRunningFlag(){
QMutexLocker locker(&m_mutex);
return m_bRunning;
}
void DataProcessor::setRunningFlag(bool bRun){
QMutexLocker locker(&m_mutex);
m_bRunning=bRun;
}
DataProcessor::DataProcessor(){start();}
DataProcessor::~DataProcessor(){
setRunningFlag(false);m_timer.stop();wait();
}
g_dataQueue
为所有线程(数据采集和分析)都可访问的一个全局变量,也就是唯一的一个DataQueue
数据队列。
最后值得一提的是,我没有在程序里面使用“内存池”,而是在编译时使用TCMALLOC,发现性能非常好。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。