在任何一门语言中,多线程都是一个相对其他方面比较重要的点,这里面的知识体系很庞大,同步和异步之间的处理方式,以及IO多路复用等等各种进行性能优化的方面,在往上层一点我们不可能一直进行系统层次的调用,这样太费时间也太麻烦,就到设计模式这里,比如反应器(Reactor)模式,再者多线程对代码的敏感程度较高,很对细微的改变可能会带来意向不到的效果,这更要求我们对于我们写的代码有更深次的理解,不仅仅是代码本身,还要求代码执行阶段锁遇到的各种问题,这就非常考验一个程序员的功底。
1.多线程及简单实例
QT5的多线程可以使用QtConcurrent和QThread两种方式来实现。
- 使用QtConcurrent: QtConcurrent提供了一种简单的方式来编写并行的代码。通过使用QtConcurrent::run()函数,可以在后台线程中执行一个函数。
#include <QtConcurrent>
#include <QDebug>
void myFunction()
{
qDebug() << "Running in background thread";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QtConcurrent::run(myFunction);
qDebug() << "Running in main thread";
return a.exec();
}
- 使用QThread: QThread类提供了一个线程对象,可以通过继承QThread来实现自定义的线程类。
#include <QThread>
#include <QDebug>
class MyThread : public QThread
{
public:
void run() override
{
qDebug() << "Running in background thread";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread;
thread.start();
qDebug() << "Running in main thread";
return a.exec();
}
以上示例中,myFunction()函数或者MyThread类的run()函数会在后台线程中执行,而主线程会继续执行下面的代码。
需要注意的是,当使用QThread时,不要直接调用run()函数,而是通过start()函数来启动线程。另外,在多线程编程中,需要注意线程安全性和共享资源的竞争问题。
2.多线程控制
2.1互斥量
在QT中,可以使用QMutex(互斥量)来控制多个线程对共享资源进行互斥访问,以避免竞争条件和数据损坏。
下面是一个使用QMutex的简单示例:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
QMutex mutex;
int sharedData = 0;
class MyThread : public QThread
{
public:
void run() override
{
mutex.lock();
// 访问共享资源
for (int i = 0; i < 10000; i++) {
sharedData++;
}
mutex.unlock();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "sharedData: " << sharedData;
return a.exec();
}
在这个示例中,我们创建了两个线程来递增sharedData
变量的值。为了确保对sharedData
的访问是互斥的,我们使用了mutex
对象。在run()
函数中,我们使用mutex.lock()
来锁定互斥量,然后执行对共享资源的访问,最后使用mutex.unlock()
来解锁互斥量。
这样就保证了同时只有一个线程可以访问共享资源,避免了数据损坏。
2.2信号量
在QT中,可以使用QSemaphore(信号量)来控制多个线程对资源的访问。信号量允许指定一个资源的数量,线程可以通过获取和释放信号量来控制对资源的访问。
下面是一个使用QSemaphore的简单示例:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>
QSemaphore semaphore(1); // 初始化信号量为1
int sharedData = 0;
class MyThread : public QThread
{
public:
void run() override
{
semaphore.acquire(); // 等待获取信号量
// 访问共享资源
for (int i = 0; i < 10000; i++) {
sharedData++;
}
semaphore.release(); // 释放信号量
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "sharedData: " << sharedData;
return a.exec();
}
在这个示例中,我们创建了两个线程来递增sharedData
变量的值。为了确保对sharedData
的访问是互斥的,我们使用了一个信号量semaphore
,并将其初始化为1。
在每个线程的run()
函数中,我们首先使用semaphore.acquire()
来等待获取信号量。一旦线程获取到信号量,它就可以访问共享资源,进行对sharedData
的递增操作。最后,线程使用semaphore.release()
来释放信号量。
由于我们将信号量初始化为1,所以只有一个线程能够获取到信号量,而另一个线程必须等待。这样就确保了对共享资源的访问是互斥的。
2.3线程等待及唤醒
在Qt中,可以使用QWaitCondition类来控制线程的等待和唤醒。QWaitCondition提供了一个条件变量,可以让线程在某个条件满足时等待,并在条件满足时被唤醒。
下面是一个使用QWaitCondition的简单示例:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QWaitCondition>
#include <QMutex>
QWaitCondition condition;
QMutex mutex;
bool ready = false;
class MyThread : public QThread
{
public:
void run() override
{
mutex.lock();
while (!ready) {
condition.wait(&mutex);
}
mutex.unlock();
qDebug() << "Thread " << QThread::currentThread() << " is running";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
// 模拟一段耗时操作
QThread::currentThread()->sleep(2);
mutex.lock();
ready = true;
condition.wakeAll();
mutex.unlock();
thread1.wait();
thread2.wait();
return a.exec();
}
在这个示例中,我们创建了两个线程来执行一段耗时操作。在主线程中,我们首先将ready
变量设置为true,然后调用condition.wakeAll()
来唤醒所有等待在条件变量上的线程。
在每个线程的run()函数中,我们首先使用mutex.lock()
来锁定互斥量。然后,线程进入一个循环,使用condition.wait(&mutex)
来等待条件变量。只有当条件变量满足时,线程才会被唤醒。一旦被唤醒,线程会打印一条消息,并继续执行。
这样,我们就实现了一种控制线程等待和唤醒的机制。主线程通过设置条件变量并唤醒等待的线程,实现了线程的同步。
3.多线程应用
3.1服务器端编程
在Qt中,可以使用QTcpServer来实现多线程的服务器端编程。下面是一个简单的示例:
#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <QThreadPool>
#include <QDebug>
class MyThread : public QThread
{
public:
MyThread(qintptr socketDescriptor, QObject *parent = nullptr)
: QThread(parent), m_socketDescriptor(socketDescriptor)
{
}
void run() override
{
QTcpSocket socket;
if (!socket.setSocketDescriptor(m_socketDescriptor))
{
emit error(socket.error());
return;
}
qDebug() << "New connection:" << socket.peerAddress().toString() << ":" << socket.peerPort();
// 处理客户端请求
QByteArray requestData = socket.readAll();
qDebug() << "Received data:" << requestData;
// 响应客户端请求
QByteArray responseData = "Hello from server!";
socket.write(responseData);
socket.waitForBytesWritten();
// 断开连接
socket.disconnectFromHost();
socket.waitForDisconnected();
qDebug() << "Connection closed:" << socket.peerAddress().toString() << ":" << socket.peerPort();
}
signals:
void error(QAbstractSocket::SocketError socketError);
private:
qintptr m_socketDescriptor;
};
class MyServer : public QTcpServer
{
Q_OBJECT
public:
MyServer(QObject *parent = nullptr)
: QTcpServer(parent)
{
}
protected:
void incomingConnection(qintptr socketDescriptor) override
{
MyThread *thread = new MyThread(socketDescriptor, this);
connect(thread, &MyThread::finished, thread, &MyThread::deleteLater);
connect(thread, &MyThread::error, this, &MyServer::handleError);
thread->start();
}
private slots:
void handleError(QAbstractSocket::SocketError socketError)
{
qDebug() << "Socket error:" << socketError;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyServer server;
if (!server.listen(QHostAddress::Any, 1234))
{
qDebug() << "Failed to start server!";
return -1;
}
qDebug() << "Server started, listening on port 1234...";
return a.exec();
}
#include "main.moc"
在这个示例中,我们创建了一个MyServer类,继承自QTcpServer。在incomingConnection()函数中,每次有新的连接到来时,我们创建一个新的MyThread线程来处理该连接。
MyThread类继承自QThread,覆盖了run()函数。在run()函数中,我们创建了一个QTcpSocket对象,并使用setSocketDescriptor()函数将其与传入的套接字描述符关联起来。然后,我们处理客户端的请求,读取请求数据,对数据进行处理,并向客户端返回响应数据。最后,断开连接。
在main()函数中,我们创建了一个MyServer对象,并通过调用listen()函数来启动服务器。通过传入监听的IP地址和端口号,来监听对应的地址和端口。如果listen()成功启动服务器,则会在控制台显示对应的提示信息。
这样,我们就实现了一个多线程的服务器端程序。每次有新的连接到来时,都会创建一个新的线程来处理该连接,实现了服务器的并发处理能力。
3.2客户端编程
在Qt中,可以使用QThread来实现多线程的客户端编程。下面是一个简单的示例:
#include <QCoreApplication>
#include <QTcpSocket>
#include <QDebug>
#include <QThread>
class MyThread : public QThread
{
public:
MyThread(QObject *parent = nullptr)
: QThread(parent)
{
}
void run() override
{
QTcpSocket socket;
socket.connectToHost("127.0.0.1", 1234);
if(!socket.waitForConnected())
{
qDebug() << "Failed to connect to server!";
return;
}
// 发送请求
QByteArray requestData = "Hello from client!";
socket.write(requestData);
socket.waitForBytesWritten();
// 接收响应
QByteArray responseData = socket.readAll();
qDebug() << "Received data: " << responseData;
// 断开连接
socket.disconnectFromHost();
socket.waitForDisconnected();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread;
thread.start();
return a.exec();
}
#include "main.moc"
在这个示例中,我们创建了一个MyThread类,继承自QThread。在run()函数中,我们创建了一个QTcpSocket对象,并使用connectToHost()函数连接到服务器端。如果连接成功,则发送请求数据,并等待响应数据的返回。最后,断开连接。
在main()函数中,我们创建了一个MyThread对象,并调用start()函数来启动线程。文章来源:https://www.toymoban.com/news/detail-815546.html
这样,我们就实现了一个多线程的客户端程序。通过在独立的线程中与服务器建立连接、发送请求和接收响应,实现了客户端的并发操作能力。文章来源地址https://www.toymoban.com/news/detail-815546.html
到了这里,关于QT基础篇(12)QT5多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!