QT系统篇(5)(下)

发布时间:2026/7/4 19:56:48
QT系统篇(5)(下) 一、多线程1.了解在Qt中多线程的处理一般是通过QThread类来实现。QThread代表一个在应用程序中可以独立控制的线程也可以和进程中的其他线程共享数据。QThread对象管理程序中的一个控制线程。2.方法3.方法waitbool 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() 也会立即返回 true4.代码//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、QMutexLocker2.互斥锁的作用互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法。在 Qt 中互斥锁主要是通过QMutex类来处理。3.QMutex和QMutexLocker的区别QMutex特点QMutex是 Qt 框架提供的互斥锁类用于保护共享资源的访问实现线程间的互斥操作。用途在多线程环境下通过互斥锁来控制对共享数据的访问确保线程安全。QMutex mutex; mutex.lock(); //上锁 //访问共享资源 //... mutex.unlock(); //解锁QMutexLocker特点QMutexLocker是 QMutex 的辅助类使用RAIIResource Acquisition Is Initialization方式对互斥锁进行加锁和解锁操作。用途简化对互斥锁的加锁和解锁操作避免忘记解锁导致的死锁等问题。QMutex mutex; { QMutexLocker locker(mutex); //在作⽤域内⾃动上锁 //访问共享资源 //... }//在作⽤域结束时⾃动解锁4.其他锁QReadLocker共享锁用于读操作上锁允许多个线程同时读取共享资源但不能写入。QWriteLocker独占锁和正常锁没区别用于写操作上锁只允许一个线程写入共享资源并且禁止其他线程读取或写入。1.2 条件变量1.了解在多线程编程中除了需要等待操作系统调度之外有时一个线程还必须等待某个特定条件满足才能继续执行这就带来了同步问题。通常的解决思路是该线程先释放互斥锁或读写锁然后进入睡眠状态让其他线程得以运行。当条件满足时另一个线程会将其唤醒。在 Qt 中专门提供了QWaitCondition类来解决像上述这样的问题。特点QWaitCondition是 Qt 框架提供的条件变量类用于线程之间的消息通信和同步。用途在某个条件满足时等待或唤醒线程用于线程的同步和协调。2.代码//.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的原因主要有两点虚假唤醒操作系统可能无故唤醒等待的线程此时条件并未满足需要重新等待。多线程竞争当线程被唤醒并重新获取锁之前其他线程可能已经改变了条件导致条件再次不满足。补充为什么使用while而不是if因为如果使用if被唤醒后肯定if条件不满足但代码不会执行else if或else分支即不会重新检查条件。此外还存在一种情况线程被唤醒后可能没有立即申请到锁需要继续等待锁到位而在等待锁的过程中条件可能再次发生变化。因此必须使用while循环反复检查条件确保在条件真正满足时才继续执行。4.方法wakeOne#include QWaitCondition void QWaitCondition::wakeOne(); // 功能 // 唤醒一个正在等待该 QWaitCondition 的线程 // 如果有多个线程在等待则随机选择一个线程唤醒 // 被唤醒的线程会重新尝试获取互斥锁QMutex并继续执行 // 参数说明 // 无参数 // 返回值 // 无voidwakeAll#include QWaitCondition void QWaitCondition::wakeAll(); // 功能 // 唤醒所有正在等待该 QWaitCondition 的线程 // 所有被唤醒的线程会依次尝试重新获取互斥锁QMutex并继续执行 // 参数说明 // 无参数 // 返回值 // 无void1.3 信号量1.了解有时在多线程编程中需要确保多个线程能够并发访问数量有限的同一类资源。例如设备内存有限我们希望需要大量内存的线程能够根据可用内存数量来决定行为。这类问题通常用信号量来解决。信号量可以看作是增强版的互斥锁不仅能完成加锁和解锁还能跟踪可用资源的数量。特点QSemaphore是 Qt 提供的计数信号量类用于控制同时访问共享资源的线程数量。用途限制并发线程数解决资源有限的场景。2.代码//.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#include QSemaphore int available() const; // 功能 // 返回当前可用的资源数量 // 参数说明 // 无参数 // 返回值 // int // 当前可用的资源数量该值永远不会为负数semaphore#include QSemaphore QSemaphore::QSemaphore( int n 0 ); // 功能 // 构造一个信号量并初始化可用的资源数量 // 参数说明 // int n 0: // 信号量初始可用的资源数量不可为负数默认为 0 // 返回值 // 无voidacquire#include QSemaphore void QSemaphore::acquire( int n 1 ); // 功能 // 尝试获取 n 个资源。如果没有足够的资源可用调用将阻塞直到资源可用 // 参数说明 // int n 1: // 需要获取的资源数量默认为 1 // 返回值 // 无voidrelease#include QSemaphore void QSemaphore::release( int n 1 ); // 功能 // 释放 n 个资源使可用资源数量增加 // 参数说明 // int n 1: // 需要释放的资源数量默认为 1 // 返回值 // 无voidtryAcquire#include QSemaphore // bool QSemaphore::tryAcquire( // int n 1 // ); // 功能 // 尝试获取 n 个资源不会阻塞而是立即返回 // 参数说明 // int n 1: // 需要尝试获取的资源数量默认为 1 // 返回值 // bool // - 若成功获取了 n 个资源返回 true // - 若当前可用资源不足返回 false且不会获取任何资源二、Qt网络1.提要在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块2.1 UDP1.QUdpSocketsocket文件2.QNetworkDategramUDP数据报数据3.代码服务器//.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; }客户端//.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 TCP1.QTcpServer用于监听端口获取客户端连接2.QTcpSocket用户端和服务器之间的数据交互3.代码服务器//.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; }客户端//.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 HTTP1.方法2.代码//.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(); }); }