一、多线程
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 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途 :在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
cppQMutex mutex; mutex.lock(); //上锁 //访问共享资源 //... mutex.unlock(); //解锁QMutexLocker:
特点 :QMutexLocker 是 QMutex 的辅助类,使用 RAII (Resource Acquisition Is Initialization)方式对互斥锁进行加锁 和解锁操作。
用途 :简化对互斥锁的加锁和解锁操作,避免忘记解锁导致的死锁等问题。
cppQMutex mutex; { QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁 //访问共享资源 //... }//在作⽤域结束时⾃动解锁
4.其他锁
- QReadLocker(共享锁)
用于读操作上锁 ,允许多个线程同时读取共享资源 ,但不能写入。
- 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的原因主要有两点:
虚假唤醒:操作系统可能无故唤醒等待的线程,此时条件并未满足,需要重新等待。
多线程竞争:当线程被唤醒并重新获取锁之前,其他线程可能已经改变了条件,导致条件再次不满足。
补充: 为什么使用
while而不是if?因为如果使用if,被唤醒后肯定if条件不满足,但代码不会执行else if或else分支(即不会重新检查条件)。此外,还存在一种情况:线程被唤醒后可能没有立即申请到锁 (需要继续等待锁到位),而在等待锁的过程中,条件可能再次发生变化。因此必须使用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();
});
}