【QT】多线程QMutex
概述
本次练习基于我之前做的图像处理小demo(点击处理按钮,三种图像处理操作同时对同一图片对象进行处理),修改了继承QThread的线程使用方式,并进行了加锁设计,三种图像处理的线程不能对同一图片同时进行操作,即当前线程处理完这个操作,其他线程才可对其进行其他操作。
创建线程方法
继承自QThread,重写void run()函数,调用成员start()启动线程,start()中可加入优先级参数。其实本来是使用继承QObject类的方法的,可是发现子线程一直启动无效,所有操作还是在主线程中依次进行的,问了其他人,说虽然QT官方推荐这种方式,但有时会是有这种不成功的情况的,建议我使用继承QThread类的这种方式,就换了这种方式重新进行加锁。
加锁方法
QMutex类提供的是线程之间的访问顺序化。QMutex的目的是保护一个对象、数据结构或者代码段,所以同一时间只有一个线程可以访问它。通常最好将互斥锁与QMutexLocker一起使用,因为这样可以很容易地确保锁定和解锁一致地执行。
实例
三个线程均继承自QThread(),为了保证互斥锁对三个线程均可见,QMutex在一个主线程CPP文件中定义,在其他线程.cpp文件中做extern声明。
Mythread.h
#pragma once
#include "qobject.h"
#include<QObject>
#include<QThread>
#include<opencv2/opencv.hpp>
#include<iostream>
#include<QPixmap>
#include<QMutex>
using namespace std;
using namespace cv;
class WorkThread :public QThread
{
Q_OBJECT
public:
void run();
void recvmat(Mat mat);
signals:
void finish(Mat mat);
private:
Mat m_mat;
};
class WorkThread2 :public QThread
{
Q_OBJECT
public:
void run();
void recvmat(Mat mat);
signals:
void finish(Mat mat);
private:
Mat m_mat;
};
class WorkThread3 :public QThread
{
Q_OBJECT
public:
void run();
void recvmat(Mat mat);
signals:
void finish(Mat mat);
private:
Mat m_mat;
};
Mythread.cpp
#include "Mythread.h"
#include "Mythread.h"
#include<QPainter>
#include<QPixmap>
#include<QAction>
#include<QPainter>
#include<QLabel>
#include<QPushButton>
#include<QDebug>
#include<opencv2/opencv.hpp>
#pragma execution_character_set("utf-8")
#include<QFileDialog>
#include<QMessageBox>
#include<QPixmap>
#include<QImage>
#include<QElapsedTimer>
#include "addlock.h"
#include<QMutex>
using namespace cv;
using namespace std;
//使用全局变量
extern QMutex g_mutex;
//高斯模糊
Mat Gaussuianintroduce(Mat m_mat)
{
//上锁
g_mutex.lock();
qDebug() << "高斯模糊处理的线程地址:" << QThread::currentThread();
QElapsedTimer time;
time.start();
Mat mat_Gussian;
GaussianBlur(m_mat, mat_Gussian, Size(29, 29), 0, 0);
//给操作延时2秒钟
waitKey(2000);
int milsec = time.elapsed();
qDebug() << "高斯模糊处理用时" << milsec << "毫秒";
//解锁
g_mutex.unlock();
return mat_Gussian;
}
//灰度处理
Mat Grayintroduce(Mat m_mat)
{
//上锁
g_mutex.lock();
qDebug() << "灰度处理的线程地址:" << QThread::currentThread();
QElapsedTimer time;
time.start();
Mat mat_Gray;
cvtColor(m_mat, mat_Gray, COLOR_BGR2GRAY);
//操作延时2秒
waitKey(2000);
int milsec = time.elapsed();
qDebug() << "灰度处理用时" << milsec << "毫秒";
//解锁
g_mutex.unlock();
return mat_Gray;
}
//边缘检测
Mat Cannyintroduce(Mat m_mat)
{
g_mutex.lock();
qDebug() << "边缘检测的线程地址:" << QThread::currentThread();//返回一个指向管理当前执行线程的QThread的指针
//QElapsedTimer主要用来测量一个操作耗时多久
QElapsedTimer time;//定义一个对象
time.start();//开始计时
//相关操作
Mat mat_Canny;
Canny(m_mat, mat_Canny, 150, 100, 3);
waitKey(2000);
//输出结果
int milsec = time.elapsed();
qDebug() << "边缘检测用时" << milsec << "毫秒";
g_mutex.unlock();
return mat_Canny;
}
void WorkThread::recvmat(Mat mat)
{
m_mat = mat;
}
void WorkThread2::recvmat(Mat mat)
{
m_mat = mat;
}
void WorkThread3::recvmat(Mat mat)
{
m_mat = mat;
}
void WorkThread::run()
{
Mat mat_Gussian = Gaussuianintroduce(m_mat);
//发送完成的信号 将信号中的图片传给主线程
emit finish(mat_Gussian);
}
void WorkThread2::run()
{
Mat mat_Gray=Grayintroduce(m_mat);
//发送完成的信号 将信号中的图片传给主线程
emit finish(mat_Gray);
}
void WorkThread3::run()
{
Mat mat_Canny = Cannyintroduce(m_mat);
//发送完成的信号 将信号中的图片传给主线程
emit finish(mat_Canny);
}
addlock.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_addlock.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
class addlock : public QMainWindow
{
Q_OBJECT
public:
addlock(QWidget *parent = Q_NULLPTR);
QImage MatToImage(Mat &mat);
signals:
void starting(Mat mat);
void destory();
private:
Ui::addlockClass *ui;
Mat image;
};
addlock.cpp
#include "addlock.h"
#include<QDebug>
#include<opencv2/opencv.hpp>
#include<QPixmap>
#include<QLabel>
#pragma execution_character_set("utf-8")
#include<QImage>
#include<QMessageBox>
#include "Mythread.h"
#include<QThread>
#include<QThreadPool>
#include<QFileDialog>
#include "Mythread.h"
using namespace std;
using namespace cv;
//锁 设为全局变量
QMutex g_mutex;
addlock::addlock(QWidget *parent)
: QMainWindow(parent)
{
ui->setupUi(this);
this->setWindowTitle("多线程图像处理");
this->setWindowIcon(QPixmap(":/res/b2.jpg"));
connect(ui->pushButton_2, &QPushButton::clicked, [=]() {
ui->label->clear();
ui->label_2->clear();
ui->label_3->clear();
ui->label_4->clear();
//调用窗口打开图片文件
//文件对话框 QFileDialog
QString filename = QFileDialog::getOpenFileName(this,
tr("open image"), ".", tr("Image file(*.png *.jpg *.bmp)")
);
//第二个参数是弹窗的标题
//第三个参数是最初显示的目录
//第三个是筛选条件 即过滤器 Image Files(*.png *.jpg *.bmp)中给出的模式匹配的文件
//将QString 转换为String 这样opencv才可读取图片
image = imread(filename.toLocal8Bit().data());
//image = imread("E:/Code/opencvcode/demo_qt_cv_thread/res/b2.jpg");
qDebug() << image.data;
if (image.empty())
{
QMessageBox::information(this, tr("提示"), tr("未成功载入图片"), QMessageBox::Ok);
}
//绘图
QPixmap pix;
pix = QPixmap::fromImage(MatToImage(image).scaled(ui->label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label->setPixmap(pix);
});
///
//创建线程
WorkThread *th1 = new WorkThread;
WorkThread2 *th2 = new WorkThread2;
WorkThread3 *th3 = new WorkThread3;
//主线程和子线程数据交互
connect(this, &addlock::starting, th1, &WorkThread::recvmat);
connect(this, &addlock::starting, th2, &WorkThread2::recvmat);
connect(this, &addlock::starting, th3, &WorkThread3::recvmat);
//开启子线程
connect(ui->pushButton, &QPushButton::clicked, [=]() {
ui->label_2->clear();
ui->label_3->clear();
ui->label_4->clear();
th1->start();
th2->start();
th3->start();
emit starting(image);
});
//利用label绘图显示
connect(th1, &WorkThread::finish, [=](Mat mat_Gussian) {
QPixmap pix2;
pix2 = QPixmap::fromImage(MatToImage(mat_Gussian).scaled(ui->label_2->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_2->setPixmap(pix2);
emit destory();
});
connect(th2, &WorkThread2::finish, [=](Mat mat_Gray) {
QPixmap pix3;
pix3 = QPixmap::fromImage(MatToImage(mat_Gray).scaled(ui->label_3->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_3->setPixmap(pix3);
});
connect(th3, &WorkThread3::finish, [=](Mat mat_Canny) {
QPixmap pix4;
pix4 = QPixmap::fromImage(MatToImage(mat_Canny).scaled(ui->label_4->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_4->setPixmap(pix4);
});
//释放线程资源
connect(this, &addlock::destroyed, this, [=]() {
//t1 为创建的子线程对象
th1->quit();
th1->wait();
th1->deleteLater();
th2->quit();
th2->wait();
th2->deleteLater();
th3->quit();
th3->wait();
th3->deleteLater();
});
}
//图片转化
QImage addlock::MatToImage(Mat &mat)
{
//输入图像为三通道
if (mat.type() == CV_8UC3)
{
//复制输入的mat数据
const uchar* pSrc = (const uchar*)mat.data;
//创建与输入Mat尺寸相同的QImage
QImage image(pSrc, mat.cols, mat.rows, (mat.cols) * 3, QImage::Format_RGB888);
//将RGB转换为BGR
return image.rgbSwapped();
}
//输入图像为单通道
else if (mat.type() == CV_8UC1)
{
QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
//设置颜色表(用于将颜色索引转换为qRgb值)
image.setColorCount(256);//灰度级数256
for (int i = 0; i < 256; i++)
{
image.setColor(i, qRgb(i, i, i));
}
//复制输入
uchar *pSrc = mat.data;
for (int row = 0; row < mat.rows; row++)
{
uchar *pDest = image.scanLine(row);//对图像的一行进行扫描,获取本行中个像素的内存地址
//复制src的内存的前num个字节内容给dest
memcpy(pDest, pSrc, mat.cols);
pSrc += mat.step;
}
return image;
}
//输入图像为四通道
else if (mat.type() == CV_8UC4)
{
qDebug() << "CV_8UC4";
// Copy input Mat
const uchar *pSrc = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
return image.copy();
}
else
{
qDebug() << "ERROR:Mat could not be converted to QImage";
return QImage();
}
}
由于灰度处理、高斯模糊、边缘检测这些算法操作处理速度已经很快了,可能上锁的效果不太明显,所以我给每个操作延时了2秒,这样效果比较明显。
当点击处理图像后,三个线程随机抢位
这个操作处理完之后,其他两个线程再随机抢占
这个操作处理完之后,最后一个线程进行处理
通过调试信息也可以看到
三种处理使用的是不同的线程地址,并且是不是同时进行操作的。
也可以看出三种图像处理算法的速度,高斯模糊最快,次而灰度处理,次而边缘检测。
遇到的问题记录一下:
对锁的理解:
假如现在有两段代码,开辟了两条线程,两条线程分别调用这两段代码,现在要求这两段代码不能同时执行,需要对代码段加锁。如果设置锁1和锁2,分别对这两段代码上锁,再开启线程的时候,依旧是同时运行的,锁1,锁2之间没有限制,必须是相同的锁才有限制的效果。
就像公共厕所的隔间,一个人进去了,其他人就无法再进入了。假如这里有个厕所隔间,里面有两个坑位,但隔间就一个门,一个人进去了,把门插住了,其他人是进不来的,只有等着个人解决完自己的事情开锁出去之后,其他人才可以再进来使用坑位。虽然里面有两个坑位,但由于门上锁了,所以其他人也无法使用另一个坑位,只能等待。