QT基础篇(12)QT5多线程

发布时间:2024年01月21日

在任何一门语言中,多线程都是一个相对其他方面比较重要的点,这里面的知识体系很庞大,同步和异步之间的处理方式,以及IO多路复用等等各种进行性能优化的方面,在往上层一点我们不可能一直进行系统层次的调用,这样太费时间也太麻烦,就到设计模式这里,比如反应器(Reactor)模式,再者多线程对代码的敏感程度较高,很对细微的改变可能会带来意向不到的效果,这更要求我们对于我们写的代码有更深次的理解,不仅仅是代码本身,还要求代码执行阶段锁遇到的各种问题,这就非常考验一个程序员的功底。

1.多线程及简单实例

QT5的多线程可以使用QtConcurrent和QThread两种方式来实现。

  1. 使用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();
}

  1. 使用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://blog.csdn.net/m0_57260797/article/details/135717542
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。