QT系统篇(5)(下)

一、多线程

1.了解

Qt 中,多线程 的处理一般是通过 QThread 类来实现。QThread 代表一个在应用程序中可以独立控制的线程 ,也可以和进程中的其他线程共享数据。QThread 对象管理程序中的一个控制线程。

2.方法

3.方法

wait

cpp 复制代码
 bool QThread::wait(
      unsigned long time = ULONG_MAX
  );

 // 功能:
 // 阻塞当前线程,直到该 QThread 对象所代表的线程执行完毕(退出),或达到指定的超时时间

 // 参数说明:
 // unsigned long time = ULONG_MAX:
 // 等待的超时时间(单位:毫秒)
 // - 若 time == ULONG_MAX(默认):无限等待,直到目标线程退出
 // - 若 time > 0:最多等待 time 毫秒,超时后返回 false

 // 返回值:
 // bool:
 // - 若目标线程已正常退出,则返回 true
 // - 若超时(time 指定的毫秒数内线程未退出),则返回 false
 // - 注意:如果目标线程尚未启动(未调用 start())或已经退出,调用 wait() 也会立即返回 true

4**.代码**

cpp 复制代码
//thread.h
class Thread : public QThread
{
	Q_OBJECT
public:
    Thread();
    void run();//重写run函数

public:
    static int num;//统计数量
    static QMutex mutex;
};

//thread.cpp

int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{}
void Thread::run()
{
    for(int i = 0;i < 10;i++)
    {
    //        mutex.lock();//上锁
    //        num++;
    //        mutex.unlock();//解锁
       QMutexLocker locker(&mutex);//上锁,当这个对象销毁后会自动解锁
       num++;
    }
}

//.cpp
Thread t1;
Thread t2;

//启动线程
t1.start();
t2.start();

//阻塞主线程,等待t1线程和t2线程返回
t1.wait();
t2.wait();
qDebug()<<Thread::num;

5.要点

  • QMutexLocker 是 Qt 中的智能锁,用于自动管理互斥量的加锁和解锁。

  • Qt 使用多线程 的意义在于,将 IO 操作 隔离出去,即保证主线程与上传/下载等操作隔离。例如游戏一边更新还能一边运行,就是因为更新(IO 操作)放在后台线程,主线程仍可处理界面和交互。

  • 服务器使用多线程,是为了充分利用 CPU 资源(避免单线程下 I/O 等待导致的 CPU 空闲)。

  • Qt 中,工作线程 不允许直接操作 UI 界面,否则会报错;而是通过信号槽 的方式(将数据发送至主线程)间接操作 UI。

6.connect拓展

connect() 函数的第五个参数为 Qt::ConnectionType ,用于指定信号和槽的连接类型 ,同时影响信号的传递方式和槽函数的执行顺序。Qt::ConnectionType 提供了以下五种方式

1.1 线程安全

1.互斥锁的种类

QMutex、QMutexLocker

2.互斥锁的作用

互斥锁 是一种保护和防止多个线程同时访问同一对象实例的方法。在 Qt 中,互斥锁主要是通过 QMutex 类来处理。

3.QMutex和QMutexLocker的区别

QMutex

  • 特点QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。

  • 用途 :在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全

cpp 复制代码
QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁

QMutexLocker

  • 特点QMutexLocker 是 QMutex 的辅助类,使用 RAII (Resource Acquisition Is Initialization)方式对互斥锁进行加锁解锁操作。

  • 用途 :简化对互斥锁的加锁和解锁操作,避免忘记解锁导致的死锁等问题。

cpp 复制代码
QMutex mutex;
{
	QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁

	//访问共享资源
	//...
 }//在作⽤域结束时⾃动解锁

4.其他锁

  1. QReadLocker(共享锁)

用于读操作上锁 ,允许多个线程同时读取共享资源 ,但不能写入

  1. QWriteLocker(独占锁,和正常锁没区别)

用于写操作上锁 ,只允许一个线程 写入共享资源,并且禁止其他线程读取或写入

1.2 条件变量

1.了解

在多线程编程中,除了需要等待操作系统调度之外,有时一个线程还必须等待某个特定条件满足 才能继续执行,这就带来了同步问题。通常的解决思路是:该线程先释放互斥锁 (或读写锁),然后进入睡眠状态 ,让其他线程得以运行。当条件满足 时,另一个线程会将其唤醒

在 Qt 中,专门提供了 QWaitCondition 类来解决像上述这样的问题。

  • 特点QWaitCondition 是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步。

  • 用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。

2.代码

cpp 复制代码
//.h
class MyWorker : public QThread
{
   Q_OBJECT
public:
   explicit MyWorker(QObject *parent = nullptr);
   // 由主线程调用,用于唤醒等待中的工作线程
   void trigger();
   // 线程入口函数,在其中等待条件满足
   void run();

private:
   QMutex m_mutex;//锁
   QWaitCondition m_cond;//条件变量
   bool m_ready;// 条件标志
};
//.cpp
void MyWorker::run()
{
   qDebug() << "Worker: 等待条件...";
   m_mutex.lock();
   while (!m_ready)
   {
       // 条件不满足,线程进入睡眠等待,同时自动释放 m_mutex,唤醒后又会自动申请锁
       m_cond.wait(&m_mutex);
   }
   m_mutex.unlock();//给没有睡眠的或者唤醒后的进行解锁
   qDebug() << "Worker: 条件满足,继续执行";
}

void MyWorker::trigger()
{
   m_mutex.lock();
   m_ready = true;     //改变条件
   m_cond.wakeOne();   //唤醒一个等待的线程
   m_mutex.unlock();
}

3.要点

  • 线程进入休眠会自动释放锁,唤醒后又会自动重新申请锁,然后从当前位置开始执行代码

  • 使用 while 而不是 if 的原因主要有两点:

  1. 虚假唤醒:操作系统可能无故唤醒等待的线程,此时条件并未满足,需要重新等待。

  2. 多线程竞争:当线程被唤醒并重新获取锁之前,其他线程可能已经改变了条件,导致条件再次不满足。

  3. 补充: 为什么使用 while 而不是 if ?因为如果使用 if ,被唤醒后肯定 if 条件不满足,但代码不会执行 else ifelse 分支(即不会重新检查条件)。此外,还存在一种情况:线程被唤醒后可能没有立即申请到锁 (需要继续等待锁到位),而在等待锁的过程中,条件可能再次发生变化。因此必须使用 while 循环反复检查条件,确保在条件真正满足时才继续执行。

4.方法

wakeOne

cpp 复制代码
 #include <QWaitCondition>

 void QWaitCondition::wakeOne();

 // 功能:
 // 唤醒一个正在等待该 QWaitCondition 的线程
 // 如果有多个线程在等待,则随机选择一个线程唤醒
 // 被唤醒的线程会重新尝试获取互斥锁(QMutex)并继续执行

 // 参数说明:
 // 无参数

 // 返回值:
 // 无(void)

wakeAll

cpp 复制代码
 #include <QWaitCondition>

 void QWaitCondition::wakeAll();

 // 功能:
 // 唤醒所有正在等待该 QWaitCondition 的线程
 // 所有被唤醒的线程会依次尝试重新获取互斥锁(QMutex)并继续执行

 // 参数说明:
 // 无参数

 // 返回值:
 // 无(void)

1.3 信号量

1.了解

有时在多线程编程中,需要确保多个线程能够并发访问数量有限的同一类资源 。例如,设备内存有限,我们希望需要大量内存的线程能够根据可用内存数量 来决定行为。这类问题通常用信号量 来解决。信号量 可以看作是增强版的互斥锁 ,不仅能完成加锁解锁 ,还能跟踪可用资源的数量

  • 特点QSemaphore 是 Qt 提供的计数信号量 类,用于控制同时访问共享资源的线程数量

  • 用途:限制并发线程数,解决资源有限的场景。

2.代码

cpp 复制代码
//.h
class SemaphoreApiDemo : public QObject
{
   Q_OBJECT
public:
   explicit SemaphoreApiDemo(QObject *parent = nullptr);
};

#endif

//.cpp
SemaphoreApiDemo::SemaphoreApiDemo(QObject *parent)
   : QObject(parent)
{
   // 1. 创建一个初始计数为 2 的信号量(同时允许两个线程访问资源)
   QSemaphore semaphore(2);
   qDebug() << "初始信号量可用计数:" << semaphore.available();

   // 2. 第一次 acquire():计数减1,变成 1
   semaphore.acquire();
   qDebug() << "执行一次 acquire() 后,计数 = " << semaphore.available();

   // 3. 第二次 acquire():计数减1,变成 0
   semaphore.acquire();
   qDebug() << "执行第二次 acquire() 后,计数 = " << semaphore.available();

   // 4. 第三次 acquire():此时计数为0,调用会阻塞。
   //    为了不卡死程序,我们用 tryAcquire() 展示非阻塞版本(返回 false 而不阻塞)。
   bool success = semaphore.tryAcquire();  // 立即返回,不阻塞
   qDebug() << "第三次尝试获取(tryAcquire)结果:" << success
            << ",计数仍为" << semaphore.available();

   // 5. 执行 release():释放一个资源,计数从0变回1
   semaphore.release();
   qDebug() << "执行一次 release() 后,计数 = " << semaphore.available();
}

3.方法

available

cpp 复制代码
#include <QSemaphore>

int available() const;

// 功能:
// 返回当前可用的资源数量

// 参数说明:
// 无参数

// 返回值:
// int:
// 当前可用的资源数量,该值永远不会为负数

semaphore

cpp 复制代码
 #include <QSemaphore>

 QSemaphore::QSemaphore(
      int n = 0
 );

 // 功能:
 // 构造一个信号量,并初始化可用的资源数量

 // 参数说明:
 // int n = 0:
 // 信号量初始可用的资源数量(不可为负数,默认为 0)

 // 返回值:
 // 无(void)

acquire

cpp 复制代码
 #include <QSemaphore>

 void QSemaphore::acquire(
      int n = 1
  );

 // 功能:
 // 尝试获取 n 个资源。如果没有足够的资源可用,调用将阻塞直到资源可用

 // 参数说明:
 // int n = 1:
 // 需要获取的资源数量(默认为 1)

 // 返回值:
 // 无(void)

release

cpp 复制代码
 #include <QSemaphore>

 void QSemaphore::release(
      int n = 1
  );

 // 功能:
 // 释放 n 个资源,使可用资源数量增加

 // 参数说明:
 // int n = 1:
 // 需要释放的资源数量(默认为 1)

 // 返回值:
 // 无(void)

tryAcquire

cpp 复制代码
 #include <QSemaphore>

 // bool QSemaphore::tryAcquire(
 //     int n = 1
 // );

 // 功能:
 // 尝试获取 n 个资源,不会阻塞,而是立即返回

 // 参数说明:
 // int n = 1:
 // 需要尝试获取的资源数量(默认为 1)

 // 返回值:
 // bool:
 // - 若成功获取了 n 个资源,返回 true
 // - 若当前可用资源不足,返回 false,且不会获取任何资源

二、Qt网络

1.提要

在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块

2.1 UDP

1.QUdpSocket(socket文件)

2.QNetworkDategram(UDP数据报数据)

3.代码

服务器

cpp 复制代码
//.h
private slots:
void ProcessRequest();//处理函数
private:
QUdpSocket* socket;//创建对象

//.cpp
//1.设置窗口标题
this->setWindowTitle("服务器");

//2.实例化socket
socket = new QUdpSocket(this);

//3.连接信号槽,处理收到的请求
connect(socket,&QUdpSocket::readyRead,this,&Widget::ProcessRequest);

//4.绑定端口
bool ret = socket->bind(QHostAddress::Any,9090);
if(!ret)
{
   QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
   return;
}
void Widget::ProcessRequest()
{
   //1.读取数据
   const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
   QString request = requestDatagram.data();

   //2.对数据进行处理
   const QString& response= Process(request);

   //3.把响应结果写到客户端
   QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(), requestDatagram.senderPort());
   socket->writeDatagram(responseDatagram);

   // 显⽰打印⽇志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
   QString::number(requestDatagram.senderPort())
        + "] req: " + request + ", resp: " + response;

        ui->listWidget->addItem(log);
}
QString Widget::Process(const QString request)
{
   return request;
}

客户端

cpp 复制代码
//.h
QUdpSocket* socket;

//.cpp
//1.设置窗口名字
this->setWindowTitle("客户端");

//2.实例化socket
socket = new QUdpSocket(this);

	//用于显示回响到客户端
//    connect(socket,&QUdpSocket::readyRead,this,[=](){
//        const QNetworkDatagram responDatagram = socket->receiveDatagram();
//        QString response = responDatagram.data();
//        ui->listWidget->addItem(QString("服务器说:")+response);
//    });
}



void Widget::on_pushButton_clicked()
{
   //1.获取输入框的内容
   const QString& text = ui->lineEdit->text();

   //2.构建请求数据
   QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT);

   //3.发送请求
   socket->writeDatagram(requestDatagram);

   //4.将消息添加到列表框
   ui->listWidget->addItem("客户端说:" + text);

   //5.清空输入框
   ui->lineEdit->setText("");
}

2.2 TCP

1.QTcpServer(用于监听端口,获取客户端连接)

2.QTcpSocket(用户端和服务器之间的数据交互)

3.代码

服务器

cpp 复制代码
//.h
public:
    QString Process(const QString&);//回响函数
private slots:
    void ProcessConnection();//处理流程
private:
    QTcpServer* tcpServer;//创建QTcpServer

//.cpp

//1.设置窗口标题
this->setWindowTitle("服务器");

//2.实例化TcpServer
tcpServer = new QTcpServer(this);

//3.通过信号槽,处理客户端建立的连接
connect(tcpServer,&QTcpServer::newConnection,this,&Widget::ProcessConnection);

//4.监听端口
bool ret = tcpServer->listen(QHostAddress::Any,9090);
if(!ret)
{
    qDebug() << "服务器启动失败" << tcpServer->serverError();
    exit(1);
}


void Widget::ProcessConnection()
{
    //1.获取新连接的对应的socket
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();

    QString log = QString("[") + clientSocket->peerAddress().toString() + ":" +     QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";

    ui->listWidget->addItem(log);

//2.通过信号槽,处理收到的请求情况
connect(clientSocket,&QTcpSocket::readyRead,this,[=]()
{
   //读取
   QString request = clientSocket->readAll();
   //根据请求处理响应
   const QString& response = Process(request);
   //把响应写回客户端
   clientSocket->write(response.toUtf8());
   QString log = QString("[") + clientSocket->peerAddress().toString()
        + ":" + QString::number(clientSocket->peerPort()) + "] req: " +
       request + ", resp: " + response;

        ui->listWidget->addItem(log);
   });

   //3.通过信号槽,处理断开连接的情况
   connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
    QString log = QString("[") + clientSocket->peerAddress().toString()
    + ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
    ui->listWidget->addItem(log);

    // 删除 clientSocket
    clientSocket->deleteLater();//等待当前槽函数及所有关联事件处理完毕后,由事件循环自动销毁
    });

}

QString Widget::Process(const QString &request)
{
    return request;
}

客户端

cpp 复制代码
//.h
private:
   QTcpSocket* tcpClient;

//.cpp
// 1. 设置窗⼝标题.
this->setWindowTitle("客⼾端");

// 2. 实例化 socket 对象.
tcpClient = new QTcpSocket(this);

// 3. 和服务器建⽴连接.
tcpClient->connectToHost(QHostAddress("127.0.0.1"), 9090);

// 4. 等待并确认连接是否出错.
if(!tcpClient->waitForConnected())
{
   qDebug()<<"连接服务器出场:" << tcpClient->errorString();
   exit(1);
}

void Widget::on_pushButton_clicked()
{
   //获取输入框内容
   const QString& text = ui->lineEdit->text();
   //把消息显示到界面上
   ui->listWidget->addItem(QString("客户端说:") + text);
   //发送给服务器
   tcpClient->write(text.toUtf8());
   //情况输入框内容
   ui->lineEdit->setText("");
}

2.3 HTTP

1.方法

2.代码

cpp 复制代码
//.h
private slots:
void on_pushButton_clicked();
private:
QNetworkAccessManager* manager;

//.cpp

manager = new QNetworkAccessManager(this);
void Widget::on_pushButton_clicked()
{
    //1.获取输入框的URL,构造QUrl对象
    QUrl url(ui->lineEdit->text());

    //2.构造HTTP请求对象(1)
    QNetworkRequest request(url);

    //3.发送GET请求(2)
    QNetworkReply* response = manager->get(request);

    // 4. 通过信号槽来处理响应
    connect(response, &QNetworkReply::finished, this, [=]()
    {
       if (response->error() == QNetworkReply::NoError)
       {
           // 响应正确
           QString html(response->readAll());
           ui->plainTextEdit->setPlainText(html);
       }
       else
       {
           // 响应出错
           ui->plainTextEdit->setPlainText(response->errorString());
       }
       response->deleteLater();
    });
}
相关推荐
Irissgwe1 小时前
第四章 QT窗口
qt
99乘法口诀万物皆可变1 小时前
PcanToVectorXL_V01:打通 Vector 与 PCAN 的双向 CAN/CAN‑FD 桥梁
c++·学习
摇滚侠1 小时前
方法 A 等方法 B 执行完再执行 叫同步调用还是异步调用 JS 默认是同步调用还是异步调用
开发语言·javascript·ecmascript
liulun1 小时前
C++ WinRT中的事件
开发语言·c++
whitelbwwww2 小时前
c++运行onnx模型
开发语言·c++
码来的小朋友2 小时前
手把手教你用 Python + PyQt5 做一个可视化图片切图工具
开发语言·python·microsoft
aaaameliaaa2 小时前
计算斐波那契数(递归、迭代)(1,1,2,3,5.....)
c语言·开发语言·笔记·算法·排序算法
m0_547486663 小时前
《模式识别:使用MATLAB分析与实现》全套PPT课件
开发语言·matlab·模式识别
Tim_103 小时前
【C++】009、extern关键字
java·开发语言