目录
本篇内容主要涉及到的相关技术有:多线程,多线程创建的方式 moveToThread,互斥锁 mutex,条件变量 condition_variable,唯一锁 unique_lock 等。
小项目需求:
在Qt程序中有2条线程,可分别输出A、B字符,每条线程只能启动一次,想办法控制输出顺序为:ABBABBABBABBABBABB共六组,输出到Ui界面上,用标签来显示即可
分析:
创建线程倒是不难,使用Qt来创建的话,可以考虑使用moveToThread的方式来实现,这个项目主要考察的是如何控制2条线程的输出顺序,观察字符的规律可以发现,每3个字符就是一个组合,3个一组,这样的话,就能拿到3这个特殊的数字,共6组数,有6个A,12个B,所以一开始可以很直白的创建2个槽函数,来分别输出A或者B就可以初步效果了,加上互斥锁和条件变量的配合,使用一个字符计数器来全局统计一下,将计数器对3取余,判断余数就可以轻松实现效果,当能除尽时,余数为0,可以输出A,当余数为1或者2时,输出B,最后可以将要输出的内容通过信号的方式发给主界面即可,只需要在主界面里面再增加一个槽函数来处理这个信号即可。
实现:
两个先简单输出的槽,就可以轻松实现出来了:
全局使用3个变量,方便操作:
int g_cnt=0;
mutex g_mtx;
condition_variable g_conv;
输出A的槽:
void MyThread::outA()
{
for(int i=0;i<6;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 == 0;});
qDebug()<<"A";
g_cnt++;
g_conv.notify_all();
}
}
输出B的槽:
void MyThread::outB()
{
for(int i=0;i<6*2;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 != 0;});
qDebug()<<"B";
g_cnt++;
g_conv.notify_all();
}
}
写完这2个核心的线程槽函数之后,发现代码居然如此雷同,重复率太高了,那是不是可以考虑优化一下代码?
答案是肯定的,必须优化,要不然此代码婶婶可忍,叔叔不可忍呀;
优化逻辑:
可以考虑把需要优化的地方给提炼出来,使用一个变量来取代,那这样就可以优化2个地方:
for(int i=0;i<6;++i) // 需要优化的第一点,把i的结束条件换成可变的
for(int i=0;i<6*2;++i) // 用另一个变量取代
for(int i=0;i<m_end;++i) // 优化后的for循环
OK,for循环的结束条件优化完成,接着往下就得考虑把输出A或者B的地方,用一个字符串变量来替换即可
qDebug()<<"A"; // 输出A
qDebug()<<"B"; // 输出B
qDebug()<<m_str; // 优化后的代码,用变量取代字符
还差一个最关键的地方,还得优化一下,等待条件也得优化一下
可以考虑把2个条件,换成类似的等价式即可
g_conv.wait(ul,[=]{return g_cnt % 3 == 0;}); // A的条件
g_conv.wait(ul,[=]{return g_cnt % 3 != 0;}); // B的条件
换成等价式:都换成双等于,并且只要满足其中一个条件就可以通过
g_conv.wait(ul,[=]{return g_cnt % 3 == m_first || g_cnt % 3 == m_second;});
最终,优化好的代码如下:
头文件需要添加的成员变量:
private:
int m_end; // 结束条件
QString m_str; // 要输出的字符
int m_first; // 条件1
int m_second; // 条件2
函数实现:
void MyThread::outAB()
{
for(int i=0;i<m_end;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 == m_first || g_cnt % 3 == m_second;});
qDebug()<<m_str;
g_cnt++;
g_conv.notify_all();
}
}
OK,代码写到此时,基本上核心的顺序控制就已经没啥问题了,剩下的就是线程通信的问题了,得把结果输出到界面的标签上,还得给线程加上信号来传递一下字符串。
在头文件中加一个信号的声明:
signals:
void sendABSignal(QString); // 发送字符串的信号
然后将该信号在线程槽中发射即可。
只需要在主界面类中加一个槽函数来接收处理即可:
头文件中的声明:
private slots:
void showABSlot(QString str); // 处理AB字符到标签的槽声明
源文件中的实现:
void ABBMainWindow::showABSlot(QString str)
{
ui->label->setText(ui->label->text()+str); // 添加到标签中
}
最终跑起来效果如下:
完整代码也贴出来吧,这样大家好参考一下:
如果不想拷贝,也可以直接下载打包好的代码:
线程类头文件:mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <mutex>
#include <condition_variable>
using namespace std;
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(int end=6,QString str="A",int first=0,int second=1,QObject *parent = nullptr);
signals:
void sendABSignal(QString); // 发送字符串的信号
public slots:
void outA(); // 控制输出A的槽
void outB(); // 控制输出B的槽
void outAB(); // 优化后的可兼容输出AB的槽
private:
int m_end; // 结束条件
QString m_str; // 要输出的字符
int m_first; // 条件1
int m_second; // 条件2
};
#endif // MYTHREAD_H
线程类源文件:mythread.cpp
#include "mythread.h"
#include <QDebug>
int g_cnt=0;
mutex g_mtx;
condition_variable g_conv;
MyThread::MyThread(int end,QString str,int first,int second,QObject *parent) : QObject(parent)
{
m_end = end;
m_str = str;
m_first = first;
m_second = second;
}
void MyThread::outA()
{
for(int i=0;i<6;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 == 0;});
qDebug()<<"A";
emit sendABSignal("A");
g_cnt++;
g_conv.notify_all();
}
}
void MyThread::outB()
{
for(int i=0;i<6*2;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 != 0;});
qDebug()<<"B";
emit sendABSignal("B");
g_cnt++;
g_conv.notify_all();
}
}
void MyThread::outAB()
{
for(int i=0;i<m_end;++i){
unique_lock<mutex> ul(g_mtx);
g_conv.wait(ul,[=]{return g_cnt % 3 == m_first || g_cnt % 3 == m_second;});
qDebug()<<m_str;
emit sendABSignal(m_str);
g_cnt++;
g_conv.notify_all();
}
}
主界面类头文件:
#ifndef ABBMAINWINDOW_H
#define ABBMAINWINDOW_H
#include <QMainWindow>
#include "mythread.h"
#include <QThread>
namespace Ui {
class ABBMainWindow;
}
class ABBMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit ABBMainWindow(QWidget *parent = 0);
~ABBMainWindow();
private slots:
void showABSlot(QString str); // 处理AB字符到标签的槽声明
private:
Ui::ABBMainWindow *ui;
MyThread *m_a;
QThread *m_aThread;
MyThread *m_b;
QThread *m_bThread;
};
#endif // ABBMAINWINDOW_H
主界面类源文件:
#include "abbmainwindow.h"
#include "ui_abbmainwindow.h"
ABBMainWindow::ABBMainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ABBMainWindow)
{
ui->setupUi(this);
// 初始化第一组对象
m_a = new MyThread(6,"A",0,0);
m_aThread = new QThread;
// 初始化第二组对象
m_b = new MyThread(12,"B",1,2);
m_bThread = new QThread;
// 使用moveToThread来处理线程的操作
m_a->moveToThread(m_aThread);
m_b->moveToThread(m_bThread);
// 优化前的槽关联
// connect(m_aThread,SIGNAL(started()),m_a,SLOT(outA())); // 最开始单个函数
// connect(m_bThread,SIGNAL(started()),m_b,SLOT(outB())); // 最开始单个函数
// 处理多线程的关联:优化后
connect(m_aThread,SIGNAL(started()),m_a,SLOT(outAB())); // 使用同一个槽 outAB 兼容2种字符
connect(m_bThread,SIGNAL(started()),m_b,SLOT(outAB()));
// 处理字符串到界面的关联
connect(m_a,SIGNAL(sendABSignal(QString)),this,SLOT(showABSlot(QString)));
connect(m_b,SIGNAL(sendABSignal(QString)),this,SLOT(showABSlot(QString)));
// 启动线程
m_aThread->start();
m_bThread->start();
}
ABBMainWindow::~ABBMainWindow()
{
delete ui;
delete m_a;
delete m_b;
m_aThread->deleteLater();
m_bThread->deleteLater();
}
void ABBMainWindow::showABSlot(QString str)
{
ui->label->setText(ui->label->text()+str); // 添加到标签中
}
主函数源文件:
#include "abbmainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ABBMainWindow w;
w.show();
return a.exec();
}