20210127:在生产者、消费者的方法中添加线程挂起方法QThread::usleep(10),使ui不卡。
20210128:在添加Track类(保存生产者Producer生成的每组数据),在ui界面中使用model-view同步显示生产者生成的数据,model-view不会对主线程造成卡顿。对消费者同样创建view,还没有进行model绑定。
避免引起主线程的阻塞,Qt在子线程中处理大数据,当多个子线程需要处理同一块数据时,需要使用数据同步,避免出现调用错乱情况,在这里我们在两个子线程使用QSemaphore作为标志位,对数组进行标识,生产者线程将生成的资源存入数组,消费者数组消耗数组内的资源,当有一方的速度过快导致数组资源耗尽时,该子线程被阻塞,直到有资源时子线程继续。代码如下:
在全局变量中声明数组、数组大小、资源总量:
constant_variable.h
#ifndef CONSTANT_VARIABLE_H
#define CONSTANT_VARIABLE_H
// 所有变量在h文件中均是声明,定义在cpp文件中
/*****************************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-24
@brief h文件声明extern变量,cpp文件定义变量
******************************************************************************************/
#include <QSemaphore>
/***************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-24
@brief 设置循环保存数据的数组大小,相当于设置“缓存”大小
***************************************************************************/
extern const int BUFFER_SIZE;
/***************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@date 2021-01-24
@brief 要读取的总资源数量,数据量大,无法一次读取完,需要长时间,分批读取
***************************************************************************/
extern const int DATA_SIZE;
/***************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@date 2021-01-24
@brief 循环使用,保存数据资源的数组
***************************************************************************/
extern char BUFFER[];
/***************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@date 2021-01-24
@brief 标识Producer可以保存资源的空余位置的数量,保存在数组中的数据量,因为初
始状态数组中没有任何数据,数组有DATA_SIZE个资源可用
***************************************************************************/
extern QSemaphore PRODUCER_SPACE;
/***************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@date 2021-01-24
@brief 标识Consumer可以使用的已保存资源的数量,因为初始时没有数据可供Consumer
使用,初始资源有0个
***************************************************************************/
extern QSemaphore CONSUMER_SPACE;
#endif // CONSTANT_VARIABLE_H
constant_variable.cpp
#include "constant_variable.h"
const int BUFFER_SIZE(4096);
const int DATA_SIZE(100000);
// 定义数组大小为BUFFER_SIZE
char BUFFER[BUFFER_SIZE];
// 初始时可用资源位BUFFER_SIZE
QSemaphore PRODUCER_SPACE(BUFFER_SIZE);
QSemaphore CONSUMER_SPACE(0);
创建窗体:
Producer按钮生成资源,保存到数组中,在左侧文本框中显示Producer所在的线程及生成的资源。Consumer按钮消耗数组中的资源,在左侧文本框中显示Consumer所在的线程及消耗的资源。这两个按钮是单独运行。Both_start_PushButton按钮同时启动两个子线程,一个生产资源,一个消耗资源,生产和消耗的都在左边的文本框中显示。
窗口代码如下:
producer_consumer_dialog.ui 代码:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Producer_consumer_dialog</class>
<widget class="QDialog" name="Producer_consumer_dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>722</width>
<height>451</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPlainTextEdit" name="producer_plainTextEdit"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="producer_pushButton">
<property name="text">
<string>Producer</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="producer_tableView"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPlainTextEdit" name="consumer_plainTextEdit"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="consumer_pushButton">
<property name="text">
<string>Consumer</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="consumer_tableView"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>348</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="both_start_pushButton">
<property name="text">
<string>Both_start_PushButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
producer_consumer_dialog.h 文件:
#ifndef PRODUCER_CONSUMER_DIALOG_H
#define PRODUCER_CONSUMER_DIALOG_H
#include <QDialog>
#include <QThread>
QT_BEGIN_NAMESPACE
class Producer_move_to_thread;
class Consumer_move_to_thread;
class Producer_table_model;
//class QCloseEvent;
QT_END_NAMESPACE
namespace Ui {
class Producer_consumer_dialog;
}
/*****************************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 创建主窗口,生成创造者producer,并在子线程-1生产资源。生成消费者consumer,并在子线程-2消耗生
产资源。资源均存储在BUFFER[]中
******************************************************************************************/
class Producer_consumer_dialog : public QDialog
{
Q_OBJECT
public:
explicit Producer_consumer_dialog(QWidget *parent = nullptr);
~Producer_consumer_dialog();
protected:
virtual void closeEvent(QCloseEvent* ev);
private:
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 初始化窗体控件
***************************************************************************/
void init_widgets();
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 初始化私有变量,将producer_,consumer_放入各自的子线程
***************************************************************************/
void init_instances();
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 实例化信号槽
***************************************************************************/
void init_connections();
private slots:
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 将字符串content设置到文本框producer_plainTextEdit
@param content 字符串
***************************************************************************/
void set_producer_plain_text(quint32 serial_number, quint64 thread_id, char ch);
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-27
@brief 将字符串content设置到文本框consumer_plainTextEdit
@param content 字符串
***************************************************************************/
void set_consumer_plain_text(QString content);
private:
Ui::Producer_consumer_dialog *ui;
Producer_move_to_thread* producer_;
Consumer_move_to_thread* consumer_;
QThread producer_thread_;
QThread consumer_thread_;
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 消费者产生数据对应的模型
***************************************************************************/
Producer_table_model* producer_table_model_;
};
#endif // PRODUCER_CONSUMER_DIALOG_H
producer_consumer_dialog.cpp文件:
#include "producer_consumer_dialog.h"
#include "ui_producer_consumer_dialog.h"
#include "producer_move_to_thread.h"
#include "consumer_move_to_thread.h"
#include "producer_table_model.h"
Producer_consumer_dialog::Producer_consumer_dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Producer_consumer_dialog)
{
ui->setupUi(this);
init_widgets();
init_instances();
init_connections();
}
Producer_consumer_dialog::~Producer_consumer_dialog()
{
delete producer_;
delete consumer_;
delete producer_table_model_;
delete ui;
}
void Producer_consumer_dialog::closeEvent(QCloseEvent *ev)
{
// QThread::sleep(3);
if ( producer_thread_.isRunning()) {
producer_thread_.quit();
producer_thread_.wait();
}
if ( consumer_thread_.isRunning()) {
consumer_thread_.quit();
consumer_thread_.wait();
}
QDialog::closeEvent(ev);
}
void Producer_consumer_dialog::init_widgets()
{
// setFixedSize(sizeHint());
resize(sizeHint());
}
void Producer_consumer_dialog::init_instances()
{
producer_ = new Producer_move_to_thread();
producer_->moveToThread(&producer_thread_);
producer_thread_.start();
consumer_ = new Consumer_move_to_thread();
consumer_->moveToThread(&consumer_thread_);
consumer_thread_.start();
producer_table_model_ = new Producer_table_model();
ui->producer_tableView->setModel(producer_table_model_);
}
void Producer_consumer_dialog::init_connections()
{
connect(ui->producer_pushButton,
&QPushButton::clicked,
producer_,
&Producer_move_to_thread::create_char_in_thread);
connect(ui->consumer_pushButton,
&QPushButton::clicked,
consumer_,
&Consumer_move_to_thread::create_char_in_thread);
connect(ui->both_start_pushButton,
&QPushButton::clicked,
ui->producer_pushButton,
&QPushButton::clicked);
connect(ui->both_start_pushButton,
&QPushButton::clicked,
ui->consumer_pushButton,
&QPushButton::clicked);
// 获取producer_生成的字符串
connect(producer_,
&Producer_move_to_thread::signal_index_id_content,
this,
&Producer_consumer_dialog::set_producer_plain_text);
connect(producer_,
&Producer_move_to_thread::signal_index_id_content,
producer_table_model_,
&Producer_table_model::add_data_to_track_list);
// 获取consumer_取得的字符串
connect(consumer_,
&Consumer_move_to_thread::signal_string,
this,
&Producer_consumer_dialog::set_consumer_plain_text);
}
void Producer_consumer_dialog::set_producer_plain_text(quint32 serial_number, quint64 thread_id, char ch)
{
QString content =
QString("%1: Producer thread id: %2, char: %3").
arg(serial_number).
arg(thread_id).
arg(ch);
ui->producer_plainTextEdit->appendPlainText(content);
// ui->producer_plainTextEdit->setPlainText(content);
}
void Producer_consumer_dialog::set_consumer_plain_text(QString content)
{
ui->consumer_plainTextEdit->appendPlainText(content);
// ui->consumer_plainTextEdit->setPlaceholderText(content);
}
生产类
producer_move_to_thread.h 文件:
#ifndef PRODUCER_MOVE_TO_THREAD_H
#define PRODUCER_MOVE_TO_THREAD_H
#include <QObject>
/*****************************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 生产者,会被移动到子线程中产生资源
******************************************************************************************/
class Producer_move_to_thread : public QObject
{
Q_OBJECT
public:
explicit Producer_move_to_thread(QObject *parent = nullptr);
~Producer_move_to_thread();
signals:
// void signal_string(quint32 serial_number, quint64 thread_id, char ch);
void signal_index_id_content(quint32 index, int id, char ch);
public slots:
void create_char_in_thread();
};
#endif // PRODUCER_MOVE_TO_THREAD_H
producer_move_to_thread.cpp 文件:
#include <QtDebug>
#include <QThread>
#include <QtGlobal>
#include "producer_move_to_thread.h"
#include "constant_variable.h"
Producer_move_to_thread::Producer_move_to_thread(QObject* parent):
QObject(parent)
{
}
Producer_move_to_thread::~Producer_move_to_thread()
{
}
void Producer_move_to_thread::create_char_in_thread()
{
for (int index(0); DATA_SIZE != index; ++index) {
// 生产1个资源(总共要产生的DATA_SZIE个资源),然后保存到BUFFER数组,BUFFER数组空余位置少1个
PRODUCER_SPACE.acquire(1);
// 计算获取资源序号、线程号、数据资源
quint32 serial_number = (quint32) (index + 1);
quint64 thread_id = (quint64) QThread::currentThreadId();
char ch = "ABCDXYZ"[uint(std::rand()) % 7];
BUFFER[index % BUFFER_SIZE] = ch;
qDebug()<< "Produce thread id: " << thread_id
<< ", char: " << ch;
// 将数据传给窗体Producer_consumer_dialog
// emit signal_string(serial_number, thread_id, ch);
emit signal_index_id_content(serial_number, thread_id, ch);
// 线程挂起,不对主线程造成卡顿
QThread::usleep(10);
// 数组中已保存1个字符,Consumer获取1个可用资源
CONSUMER_SPACE.release(1);
}
}
资源消耗类
consumer_move_to_thread.h 文件:
#ifndef CONSUMER_MOVE_TO_THREAD_H
#define CONSUMER_MOVE_TO_THREAD_H
#include <QObject>
class Consumer_move_to_thread : public QObject
{
Q_OBJECT
public:
explicit Consumer_move_to_thread(QObject *parent = nullptr);
signals:
void signal_string(QString content);
public slots:
void create_char_in_thread();
};
#endif // CONSUMER_MOVE_TO_THREAD_H
consumer_move_to_thread.cpp文件:
#include <QtDebug>
#include <QThread>
#include "consumer_move_to_thread.h"
#include "constant_variable.h"
Consumer_move_to_thread::Consumer_move_to_thread(QObject *parent) :
QObject(parent)
{
}
void Consumer_move_to_thread::create_char_in_thread()
{
for (int index(0); DATA_SIZE != index; ++index) {
// 消耗1个资源(总共要消耗DATA_SZIE个资源),从BUFFER数组中取出,BUFFER数组空余位置多1个。当
// 没有资源提供时,进程被挂起,待有足够资源后再执行后续程序
CONSUMER_SPACE.acquire(1);
qDebug()<< "Consumer thread id: " << (quint64) QThread::currentThreadId()
<< ", char: " << (char) BUFFER[index % BUFFER_SIZE];
QString content =
QString("%1: Consumer thread id: %2, char: %3").
arg(index + 1).
arg((quint64) QThread::currentThreadId()).
arg(BUFFER[index % BUFFER_SIZE]);
emit signal_string(content);
//线程挂起,不对主线程造成卡顿
QThread::usleep(10);
// 使用数组中1个资源,数组释放1个空间,给生产者提供1个空余位置
PRODUCER_SPACE.release(1);
}
}
保存生产者生成的数据Track类:
Track.h
#ifndef TRACK_H
#define TRACK_H
#include <QtGlobal>
/*****************************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 数据类,保存随机生成的资源
******************************************************************************************/
class Track
{
public:
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 构造函数
@param index 数据的生成序列号
@param thread_id 生成数据的线程号
@param ch 生成的资源数据
***************************************************************************/
explicit Track(quint32 index = 0, quint64 thread_id = 0, char ch = 'A');
~Track();
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 拷贝构造函数
***************************************************************************/
Track(const Track& r_value);
/***************************************************************************
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief 赋值操作符
***************************************************************************/
Track& operator = (const Track& r_value);
quint32 get_index() const;
quint64 get_thread_id() const;
char get_char() const;
private:
quint32 index_;
quint64 thread_id_;
char char_;
};
#endif // TRACK_H
Track.cpp
#include "track.h"
Track::Track(quint32 index, quint64 thread_id, char ch):
index_(index),
thread_id_(thread_id),
char_(ch)
{
}
Track::~Track()
{
}
Track::Track(const Track &r_value)
{
index_ = r_value.index_;
thread_id_ = r_value.thread_id_;
char_ = r_value.char_;
}
Track &Track::operator =(const Track &r_value)
{
if (this != &r_value) {
index_ = r_value.index_;
thread_id_ = r_value.thread_id_;
char_ = r_value.char_;
}
return *this;
}
quint32 Track::get_index() const
{
return index_;
}
quint64 Track::get_thread_id() const
{
return thread_id_;
}
char Track::get_char() const
{
return char_;
}
生产者模型Produer_table_model类:
Produer_table_model.h
#ifndef PRODUCER_TABLE_MODEL_H
#define PRODUCER_TABLE_MODEL_H
#include <QAbstractTableModel>
#include <QObject>
#include "track.h"
/*****************************************************************************************
@copyright 2013-2020
@author qiaowei
@contact weiweiqiao@126.com
@version 1.0
@date 2021-01-28
@brief
******************************************************************************************/
class Producer_table_model : public QAbstractTableModel
{
Q_OBJECT
public:
explicit Producer_table_model(QObject* parent = nullptr);
~Producer_table_model();
void add_data_to_track_list(quint32 serial_number, quint64 thread_id, char ch);
QList<Track>* get_track_list();
protected:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
private:
QList<Track>* track_list_;
};
#endif // PRODUCER_TABLE_MODEL_H
Produer_table_model.cpp
#include "producer_table_model.h"
Producer_table_model::Producer_table_model(QObject* parent):
QAbstractTableModel(parent),
track_list_(new QList<Track>())
{
}
Producer_table_model::~Producer_table_model()
{
delete track_list_;
}
void Producer_table_model::add_data_to_track_list(quint32 serial_number, quint64 thread_id, char ch)
{
track_list_->append(Track(serial_number, thread_id, ch));
QModelIndex header_model_index = createIndex(serial_number - 1, 0);
QModelIndex tail_model_index = createIndex(serial_number - 1, 2);
layoutChanged();
}
QList<Track> *Producer_table_model::get_track_list()
{
return track_list_;
}
int Producer_table_model::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
if ( track_list_) {
return track_list_->count();
} else {
return 0;
}
}
int Producer_table_model::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
// // 序号、线程号、数据资源
// return 3;
// 线程号、数据资源
return 2;
}
QVariant Producer_table_model::data(const QModelIndex &index, int role) const
{
if ( !index.isValid()) {
return QVariant();
}
QVariant v;
if (Qt::DisplayRole == role) {
switch (index.column()) {
case 0:
v = track_list_->at(index.row()).get_thread_id();
break;
case 1:
v = (QChar) track_list_->at(index.row()).get_char();
break;
}
return v;
}
return QVariant();
}
QVariant Producer_table_model::headerData(int section, Qt::Orientation orientation, int role) const
{
if ((Qt::DisplayRole == role) &&
(Qt::Horizontal == orientation)) {
QString header_content;
switch (section) {
case 0:
header_content = tr("线程号");
break;
case 1:
header_content = tr("数据");
break;
}
return header_content;
}
return QAbstractTableModel::headerData(section, orientation, role);
}
在这里特别注意PRODUCER_SPACE和CONSUMER_SPACE的初始值,因为资源BUFFER数组初始资源为0,所以生产者Producer可用的数组空间为所有,消耗着Consumer可用的数组空间为0,因为没有资源可用。
运行结果如下图:
注:因为数据在窗体控件上显示只能在主线程运行,当大量数据在窗体控件上设置时,会是的窗体有卡顿情况,该怎么改进避免窗体卡顿,有谁知道该怎么改进么??