在 Qt 中进行多线程编程有多种方式,每种方式都有其独特的特点和适用场景。
QThread 的 run 方法特点:
QThread 类,并重写 run 方法。run 方法中的代码。使用场景: 适合需要自定义线程行为、复杂的连续性操作,如图像处理、复杂计算。
示例:图像处理线程
#include #include class ImageProcessingThread : public QThread { Q_OBJECT protected: void run() override { // 假设进行一些复杂的图像处理操作 for (int i = 0; i < 100; ++i) { qDebug() << "Processing image:" << i; QThread::sleep(1); } } }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); ImageProcessingThread thread; thread.start(); thread.wait(); // 等待线程完成 return app.exec(); }
moveToThread 方法特点:
QObject 类的子类一起使用。使用场景: 适用于需要将对象的槽函数或事件处理放到另一个线程中执行,如网络请求处理、定时任务。
示例:网络请求处理
#include #include #include #include #include // 客户端处理类 class ClientHandler : public QObject { Q_OBJECT public: // 构造函数,传入 socket 描述符 ClientHandler(qintptr socketDescriptor, QObject *parent = nullptr) : QObject(parent), socketDescriptor(socketDescriptor) {} public slots: // 处理客户端请求 void handleClient() { QTcpSocket socket; if (!socket.setSocketDescriptor(socketDescriptor)) { qDebug() << "设置 socket 描述符失败"; return; } qDebug() << "客户端已连接: " << socketDescriptor; socket.write("来自服务器的问候\n"); socket.waitForBytesWritten(); while (socket.waitForReadyRead()) { QByteArray data = socket.readAll(); qDebug() << "收到客户端消息:" << data; socket.write("回显: " + data); socket.waitForBytesWritten(); } qDebug() << "客户端已断开: " << socketDescriptor; socket.disconnectFromHost(); } private: qintptr socketDescriptor; // 保存客户端 socket 描述符 }; // 自定义 TCP 服务器类 class MyTcpServer : public QTcpServer { Q_OBJECT protected: // 重写 incomingConnection 函数来处理新的连接 void incomingConnection(qintptr socketDescriptor) override { QThread *thread = new QThread; // 为每个连接创建一个新线程 ClientHandler *handler = new ClientHandler(socketDescriptor); handler.moveToThread(thread); connect(thread, &QThread::started, handler, &ClientHandler::handleClient); connect(handler, &ClientHandler::destroyed, thread, &QThread::quit); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread.start(); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); MyTcpServer server; if (!server.listen(QHostAddress::Any, 12345)) { qDebug() << "服务器启动失败:" << server.errorString(); return 1; } qDebug() << "服务器已启动,端口:" << server.serverPort(); return app.exec(); } #include "main.moc" 客户端代码(Client)
#include #include #include int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QTcpSocket socket; socket.connectToHost("127.0.0.1", 12345); if (!socket.waitForConnected(3000)) { qDebug() << "连接服务器失败:" << socket.errorString(); return 1; } qDebug() << "已连接到服务器"; socket.write("来自客户端的问候\n"); socket.waitForBytesWritten(); if (socket.waitForReadyRead(3000)) { QByteArray data = socket.readAll(); qDebug() << "收到服务器消息:" << data; } socket.disconnectFromHost(); return app.exec(); } QtConcurrent特点:
使用场景: 适用于并行处理容器数据、并行函数执行,如批量数据处理、并行计算。
示例:并行处理数据
我们将定义一个简单的函数 processItem 来处理每个元素。然后使用 QtConcurrent::map 并行处理 QList 容器中的每个元素。
#include #include #include #include #include #include // 定义处理每个元素的函数 void processItem(int &item) { item *= 2; // 示例操作:将每个元素乘以2 QThread::sleep(1); // 模拟耗时操作 qDebug() << "处理项目:" << item << " 线程:" << QThread::currentThread(); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 创建一个包含初始数据的列表 QList list = {1, 2, 3, 4, 5}; // 使用 QtConcurrent::map 并行处理列表中的每个元素 QFuture future = QtConcurrent::map(list, processItem); // QFutureWatcher 用于监视 QFuture 的完成状态 QFutureWatcher watcher; QObject::connect(&watcher, &QFutureWatcher::finished, [](){ qDebug() << "所有项目已处理完毕"; QCoreApplication::quit(); }); watcher.setFuture(future); // 将 QFutureWatcher 绑定到 QFuture return app.exec(); } QThreadPool 和 QRunnable特点:
使用场景: 适用于任务队列、多任务执行,如后台任务处理、批量任务执行。
示例:后台任务处理
#include #include #include #include // 定义一个简单的任务类,继承自 QRunnable class MyTask : public QRunnable { public: void run() override { qDebug() << "任务执行在线程:" << QThread::currentThread(); // 示例任务操作,这里可以是任何需要在后台执行的任务 for (int i = 0; i < 5; ++i) { qDebug() << "任务" << taskId << "执行步骤" << i; QThread::sleep(1); // 模拟耗时操作 } } void setTaskId(int id) { taskId = id; } private: int taskId; }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QThreadPool threadPool; threadPool.setMaxThreadCount(5); // 设置线程池中最大线程数 // 创建并提交多个任务到线程池 for (int i = 0; i < 3; ++i) { MyTask *task = new MyTask(); task->setTaskId(i + 1); // 设置任务ID,用于区分任务 threadPool.start(task); // 启动任务,线程池会自动分配线程执行任务 } // 等待所有任务完成 threadPool.waitForDone(); qDebug() << "所有任务已完成"; return app.exec(); } 例如处理文件、网络请求等复杂任务。
#include #include #include #include #include #include // 定义一个处理文件的任务 class FileTask : public QRunnable { public: FileTask(const QString &fileName) : m_fileName(fileName) {} void run() override { qDebug() << "处理文件任务:" << m_fileName << "在线程:" << QThread::currentThread(); QFile file(m_fileName); if (file.open(QIODevice::ReadOnly)) { qDebug() << "文件内容:" << file.readAll(); file.close(); } else { qDebug() << "无法打开文件:" << m_fileName; } } private: QString m_fileName; }; // 定义一个处理网络请求的任务 class NetworkTask : public QRunnable { public: NetworkTask(const QString &hostName, quint16 port) : m_hostName(hostName), m_port(port) {} void run() override { qDebug() << "处理网络请求任务:" << m_hostName << ":" << m_port << "在线程:" << QThread::currentThread(); QTcpSocket socket; socket.connectToHost(m_hostName, m_port); if (socket.waitForConnected()) { socket.write("GET / HTTP/1.0\r\n\r\n"); if (socket.waitForReadyRead()) { qDebug() << "服务器响应:" << socket.readAll(); } socket.disconnectFromHost(); } else { qDebug() << "无法连接到服务器:" << m_hostName << ":" << m_port; } } private: QString m_hostName; quint16 m_port; }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QThreadPool threadPool; threadPool.setMaxThreadCount(3); // 设置线程池中最大线程数 // 创建并提交文件处理任务 FileTask *fileTask = new FileTask("example.txt"); threadPool.start(fileTask); // 创建并提交多个网络请求任务 NetworkTask *task1 = new NetworkTask("www.example.com", 80); NetworkTask *task2 = new NetworkTask("api.example.com", 443); threadPool.start(task1); threadPool.start(task2); // 等待所有任务完成 threadPool.waitForDone(); qDebug() << "所有任务已完成"; return app.exec(); } FileTask 处理文件任务,读取并输出文件内容。NetworkTask 处理网络请求任务,连接到指定主机和端口,并输出服务器响应。
特点:
使用场景: 适用于跨线程通信、线程间数据交换,如定时任务、多线程数据更新。
示例:多线程数据更新
#include #include #include #include #include // 定义一个 Worker 类,负责执行定时任务并发送数据更新信号 class Worker : public QObject { Q_OBJECT public: Worker() { timer.setInterval(1000); // 设置定时器间隔为 1 秒 connect(&timer, &QTimer::timeout, this, &Worker::doWork); // 定时器超时信号连接到槽函数 timer.start(); // 启动定时器 } signals: // 定义一个信号,用于发送数据更新信号 void dataUpdated(const QString &data); private slots: // 槽函数,模拟定时任务执行 void doWork() { // 模拟生成数据 QString newData = QString("数据更新时间:%1").arg(QDateTime::currentDateTime().toString()); qDebug() << "Worker 发送数据更新信号:" << newData; // 发送数据更新信号到主线程 emit dataUpdated(newData); } private: QTimer timer; // 定时器对象 }; // 定义一个 Controller 类,作为主线程的控制器,处理数据更新信号 class Controller : public QObject { Q_OBJECT public: Controller() { // 连接 Worker 的数据更新信号到处理函数 connect(&worker, &Worker::dataUpdated, this, &Controller::handleDataUpdated); } public slots: // 槽函数,处理数据更新信号 void handleDataUpdated(const QString &data) { qDebug() << "Controller 接收到数据更新:" << data << " 在线程:" << QThread::currentThread(); // 在这里进行主线程的数据更新操作,例如更新 UI emit updateUI(data); // 发送信号到主线程更新 UI } signals: // 定义一个信号,用于更新主线程的 UI void updateUI(const QString &data); private: Worker worker; // Worker 对象,负责定时任务和数据生成 }; // 定义一个 UI 类,负责接收 Controller 发送的更新信号并更新 UI class UI : public QObject { Q_OBJECT public: UI() { // 连接 Controller 的更新 UI 信号到处理函数 connect(&controller, &Controller::updateUI, this, &UI::updateUI); } public slots: // 槽函数,更新 UI void updateUI(const QString &data) { qDebug() << "UI 更新数据:" << data << " 在线程:" << QThread::currentThread(); // 在这里进行实际的 UI 更新操作,例如在界面上显示数据 } private: Controller controller; // Controller 对象,负责数据处理和信号传递 }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); UI ui; // 创建 UI 对象,负责接收和显示数据 return app.exec(); } #include "main.moc" 代码解释:
Worker 类:负责定时任务的执行。在构造函数中设置定时器 timer 的间隔为 1 秒,并将定时器的 timeout 信号连接到 doWork 槽函数。每次定时器超时时,会生成一个带有当前时间的数据,并发送 dataUpdated 信号到主线程。Controller 类:作为主线程的控制器,连接 Worker 的 dataUpdated 信号到 handleDataUpdated 槽函数。当接收到 dataUpdated 信号时,会在槽函数中处理数据更新操作,并通过 updateUI 信号发送到主线程。UI 类:负责接收 Controller 发送的 updateUI 信号,并在 updateUI 槽函数中实现实际的 UI 更新操作。在这个示例中,简单地打印接收到的数据和线程信息。