第八章
线程与进程的区别:
进程与单个程序类似,可以由操作系统直接执行;
线程是进程的一个子集,也就是一个进程可包含多个线程;
通常情况下,不同的进程彼此是无关的,而不同的线程共享内存和资源(进程可以通过操作系统提供的手段实现彼此交互)
8.1 Qt中的多线程
Qt提供的命名空间、类和函数:
QThread:所以线程的基类,可以从他的派生子类创建新的线程
QThreadPool:可以用于管理线程,并且可以重用已有线程来实现新的功能
QRunnable:提供创建线程的另一种方法,
QMutex、QMutexLocker、QSemaphore、QWaitCondition、QReadLocker、QWriteLocker和QWriteLocker:处理线程间到的同步任务
QtConcurrent:一个命名空间,可用于高级API创建多线程应用程序
Qfuture、QfutureQatcher、Qfututelterator和QFutureSynchronizer:这些类与QtConcurrent命名空间共同使用,可以处理多线程以及异步操作结果
8.2 利用QThread实现低级多线程
通过利用Qt中可用的两种不同方法来使用QThread类,首先子类化并重写run方法,然后使用所有的Qt对象中可用的moveToThread函数。
8.2.1 子类化QThread
class VideoProcessorThread : public QThread
{
Q_OBJECT
public:
explicit VideoProcessorThread(QObject *parent = nullptr);signals:
void inDisplay(QPixmap pixmap);
void outDisplay(QPixmap pixmap);public slots:
private:
void run() override;};
注意run函数只能在内部调用,因此只需重新实现它,用到以下函数:
start:用于启动一个未启动的线程
terminate:强制终止线程,(不推荐使用)
setTerminationEnable:可用于允许或禁止terminate
wait:该函数用于阻塞线程(强制等待),直到线程完成或者达到超时值
requestInterruption和isRequestInterrupted:可以用于设置和获取中断请求状态
isRunning和isFinished:请求线程的执行状态
然后在MainWindow中使用线程,包含头文件,然后再私有成员里面添加
VideoProcessorThread processor;
连接信号,放在构造函数中,setupUi代码前
connect(processor, SIGNAL(inDisplay(QPixmap)),
ui->inVideo, SLOT(setPixmap(QPixmap)));processor.start();
在MainWindow的析构函数中,delete ui之前,
processor.requestInterruption();
processor.wait();
8.2.2 使用moveToThread函数
使用moveToThread函数可以确保它在一个单独的线程中运行。
processor = new VideoProcessor();
processor->moveToThread(new QThread(this));
然后使用startVideo和stopVideo函数用来启动和终止对来自默认摄像头的处理
//构造函数中
processor->thread()->start();
//析构函数中
processor->stopVideo();
processor->thread()->quit();
processor->thread()->wait();
注意在构造函数中没有分配任何父函数,并且还确保将其定义为指针,有父对象的对象不能移动到新线程中。
8.3 线程同步工具
多线程往往要解决线程间的冲突以及因为并行而产生的问题。解决这些问题的类被称为线程同步工具。
8.3.1 互斥锁
forever{
image = imread"image.jpg";
}
forever宏可以用于创建无限循环。
然后另一个线程一直在处理这个图像
forever{
cvtColor(image, image, CV_BGR2GRAY);
resize(image, image, Size(), 0.5, 0.5);
}
如果两个线程同时运行,就有可能在,某一时刻调用图片冲突,因此加入锁来解决,确保线程同时只有一个线程在访问该对象
forever{
imageMutex.lock();
image = imread"image.jpg";
imageMutex.unlock();
}
forever{
imageMutex.lock();
cvtColor(image, image, CV_BGR2GRAY);
resize(image, image, Size(), 0.5, 0.5);
imageMutex.unlock();
}
最好用QMutexLocker类来重写锁
forever{
QMutexLocker locker(&imageMutex);
image = imread"image.jpg";
}
forever{
QMutexLocker locker(&imageMutex);
cvtColor(image, image, CV_BGR2GRAY);
resize(image, image, Size(), 0.5, 0.5);
}
8.3.2 读写锁
假设希望不同的线程能够同时读取对象(例如变量、类实例、文件等);但是希望保证只有一个线程可以在任何给的给定的时间修改(或者写入)该对象。Qt提供了QReadWriteLock类,像QMutex类一样使用,区别在于它提供了一个用与读取的锁函数(lockForRead)和另一个用于写入的锁函数(lockForWrite)。特性如下:
如果线程中的调用了lockForRead函数,其他线程仍然可以调用lockForRead并为了读取而访问敏感对象(指的是正在使用锁的对象)。
此外,如果在线程中带哦用lockForRead函数,则任何调用lockForWrite的线程都将被阻塞,直到该线程调用解锁(unlock)为止。
如果在线程中调用lockForWrite函数,则所有其他线程都将被阻塞,直到解锁
如果在线程中调用lockForWrite函数,而前一个线程有一个读取锁,那么所有调用lockForRead的新线程都必须等待需要写入锁得线程,因此需要lockForWrite的线程都将被赋予更高级的优先级。
8.3.3 信号量
信号量类似增强的互斥锁,不仅能完成锁定和解锁操作,而且还可以跟踪可用资源数量,Qt提供了一个名为Qsemaphore类,可用函数如下:
acquire:可以获得特定数量的所需资源
release:可以用来释放已经使用并且不在需要的特定数量的资源
available:可以用来获取可用资源的数量
//创建100兆字节的可用内存范围
QSemaphore memSem(100);
//在每个线程中,获取并释放内存
memSem.acquire(X);
process_image();
memSem.release(X);
8.3.4 等待条件
Qt框架有一个名为QWaitCondition类,可以让线程在满足一些其他条件之后,才继续进行,例如两个线程,一个创造图像,一个修改图像,如果图像未被创造出来,那么即时轮到该线程也无法进行后续步骤。
forever{
mutex.lock();
imageExistsCond.wait(&mutex);
read_image();
mutex.unlock();
}
//另一个线程
forever{
if(QFile::exits("image.jpg")){
iamgeExistsCond.wakeAll(); //唤醒
}
}
8.4基于QtConcurrent的高级多线程
Qt还提供高级API,用于在不需要处理线程同步工具的情况下创建多线程程序。下面的函数通常使用高级QtConcurrent API,用来处理多线程:
filter:用于滤波列表,提供含滤波数据的列表和一个滤波函数
filtered:与filter的工作方式类似,区别是返回filtered滤波后的列表,而不是更新输入列表
filteredReduced:与filtered相似,但是filteredReduced还应用第二个函数将每一项传给滤波器
map:可用于将一个特定的函数应用到一个列表的所有项
mapped:与map的工作方式类似,区别是返回mapped结果列表,而不是更新输入列表
mappedReduced:与mapped相似,但是mappedReduced还将第二个函数应用到mapped函数之后的每一项
run:在一个单独线程中轻松执行这个函数
对ConcurrentCV程序的总结:
QFutureWatcher类是监控的QtConcurrent计算的一种便捷方法,并将之分配给一个Qfuture类,在这种情况下QfutureWatcher被定义为指针,并且在完成进程之后将其删除,原因是QFutureWatcher必须在进程继续的过程中保持激活状态,并且只有完成计算之后才能删除。因此,首先建立QFutureWatcher所需的所有连接,然后,设置相应的future变量,在建立连接之后,确保设定了future是很重要的。
QFuture包含以下用于控制计算的函数:
pause、resume、cancel
以下函数检索计算状态:
isStarted、isPaused、isRunning、isFinished、isCanceled
使用以下函数设置QtConcurrent函数的最大线程数:
QThreadPool::globalInstance()->setMaxThreadCount(n);
也可以定义筛选函数
bool filterImage(QFileInfo &info){
if(info.created().date.year()<2015)
true;
else
false;
}
//调用
QtConcurrent::filter(list,filterImage);
//或者
QtConcurrent::filteredReduced(list,filterImage,addDateTime);