Qt高并发

QThread是一个低级(low-level)类,适合用于显式地构建长期运行的线程。

     QtConcurrent是一个命名空间,提供了用于编写并发软件的更高层次的类和算法。该命名空间中有一个重要的类,QThreadPool,这是一个管理线程池的类。每个Qt应用程序都有一个QThreadPool::globalInstance()函数,它带有一个推荐的最大线程数,在大多数系统上,处理核的数量就是该值的默认值。

     借助于QtConcurrent中函数式的map/filter/reduce算法(它们可将函数并行用到容器中的每一项),通过将进程分布在由线程池管理的多个线程上,可编写一个能够自动利用系统多核的程序。

线程指南

     一般情况下,要尽可能避免使用多线程,而是用Qt事件循环与QTimer、非阻塞I/O操作、信号以及短持续时间槽相结合的方法来代替。此外,可以在主线程中长期运行的循环调用QApplication::processEvents(),以使执行工作时图形用户界面可以保持响应。

    要驱动动画(animation),建议使用QTimer,QTimeLine或者动画框架(Animation Framework)。这些API并不需要额外创建其它线程。它们允许访问动画代码中的GUI对象而且不会妨碍图形用户界面的响应。

    如果要完成CPU密集型工作并希望将其分配给多个处理核,可以把工作分散到QRunnable并通过以下这些推荐做法来实现线程的安全。

     1)无论何时,都尽可能使用QtConcurrent算法把CPU密集型计算工作分散给多线程,而不是自己编写QThread代码。

     2)除了主线程以外,不要从其它任何线程访问图形用户界面(这也包括那些由QWidget派生的类、QPixmap和那些与显卡相关的类)。这包括读取操作,比如查询QLineEdit中输入的文本。

     3)要其他线程中处理图像,使用QImage而不是QPixmap。

     4)不要调用QDialog::exec()或者从除主线程之外的任何线程创建QWidget或QIODevice的子类。

     5)使用QMutex、QReadWriteLock或者QSemaphone以禁止多个线程同时访问临界变量。

     6)在一个拥有多个return语句的函数中使用QMutexLocker(或者QReadLocker、QWriteLocker),以确保函数从任意可能的执行路径均可释放锁。

     7)创建QObject的线程,也称线程关联(thread affinity),负责执行那个QObject的槽。

     8)如果各QObject具有不同的线程关联,那么就不能以父—子关系来连接它们。

     9)通过从run()函数直接或者间接调用QThread::exec(),可以让线程进入事件循环。

    10)利用QApplication::postEvent()分发事件,或使用队列式的信号/槽连接,都是用于线程间通信的安全机制——但需要接收线程处于事件循环中。

    11)确保每个跨线程连接的参数类型都用qRegisterMetaType()注册过。

线程安全和QObject

    可重入(reentrant)函数就是一个可以由多个线程同时调用的函数,其中任意的两次调用都不会试图访问相同的数据。线程安全的方法在任何时间都可以同时由多个线程调用,因为任何共享数据都会在某种程度上(例如,通过QMuex)避免被同时访问。如果一个类的所有非静态函数都是可重入的或者是线程安全的,那么它就是可重入的或者是线程安全的。

    一个QObject在它所”属于“或者有关联的线程中被创建。其各子对象也必须属于同一线程。Qt禁止跨线程的父——子关系。

   1)QObject::thread()可返回它的所有者线程,或者是其关联线程。

   2)QObject::moveToThread()可将其移动到另一个线程

moveToThread(this)

   由于QThread是一个QObject而且在需要额外的线程时才会创建QThread,因此,即使你会认为QThread和线程是可以相互指代的,也是可以理解的。尽管如此,那个额外的线程在调用QThread::start()之前实际上都不会被创建,这使得问题更难于理解。

   回想一下,每个QThread的本质都是一个QObject,这决定了它与其创建的线程存在关联,而不是与它启动的线程存在关联。

   正是因为这个原因,有人说QThread并不是线程本身,而是该线程的管理器。这或许也可以有助于理解这一方式。实际上,QThread是一个底层线程API的封装器,也是一个基于java.lang.thread API的管理单个线程的管理器。

   这就意味着,当信号连接到这个QThread的槽上时,槽函数的执行是在其创建线程,而不是在其管理的线程进行的。

   一些程序通过改变QThread的定义使它可表示其管理的线程并在该线程内执行执行它的槽。这些程序使用一种变通方法:在QThread的构造函数中使用moveToThread(this)。这一变通方法的主要问题是,在线程退出后,通过post方式派发给该对象的事件如何处理留下不确定性。

   线程安全的对象就是一个可以由多个线程同时访问并且可确保处于”有效“状态的对象。默认情况下,QObject不是线程安全的。为了让一个对象线程安全,可以利用以下方法。

   1)QMutex用于保证互斥,可与QMutexLocker一起使用,它允许一个单独的线程T保护(锁住)一个对象或者一段代码,使其在线程T释放(解锁)之前不能被其它的线程访问。

   2)QWaitCondition与QMutex结合使用,可以把某个线程置于一种不忙的阻塞状态,这种状态下,可让其等待另外一个线程将其唤醒。

   3)QSemaphore是一个广义的QMutex,可以用在一个线程在开始工作之前需要锁住不止一个资源的各种情况。信号量使其能够保证线程仅在要进行工作所需的资源全部满足的情况下才锁住资源。

moveToThread的使用示例

 

class Worker : public QObject
{
    Q_OBJECT
 
public slots:
    void doWork(const QString ¶meter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }
 
signals:
    void resultReady(const QString &result);
};
 
class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};


     在Worker槽函数内部的代码将会在单独的线程内执行。
   另外一种让代码在单独的线程内运行的方法是子类化QThread并重新实现run()函数。比如:

class WorkerThread : public QThread
{
    Q_OBJECT
    void run() Q_DECL_OVERRIDE {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }
signals:
    void resultReady(const QString &s);
};
 
void MyObject::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}


   在示例中,线程会在run()函数返回后退出。除非你调用exec()函数,否则不会有任何事件循环运行在线程内。


   重要的是记住一个QThread实例驻留在实例化它的旧线程中,而不是在调用run()函数的新线程中。这意味着所有QThread的排队槽函数都会旧线程内执行。因此,想要在新线程中调用槽函数的开发者必须使用worker对象的方法;新的槽函数不应该被直接实现成子类化的QThread。

   当子类化QThread时,记住构造函数是在旧线程中执行的,而run()函数是在新线程中执行的。如果一个成员变量被两个函数访问,那么变量是被两个不同的线程访问。这时得检查这样做是否安全。

在次线程中使用Qt的类

    当函数可以同时被不同的线程安全地调用时,就称其为”线程安全的“(thread-safe)。如果在不同的线程中对某一共享数据同时调用两个线程安全的函数,那么结果将总是可以确定的。若将这个概念推广,当一个类的所有函数都可以同时被不同的线程调用,并且它们之间互不干涉,即使是在操作同一个对象的时候也互不妨碍,我们就把这个类称为是”线程安全的“。

    一个类是否是可重入的,在Qt的参考文档中有标记。通常情况下,任何没有被全局引用或者被其他共享数据引用的C++类都认为是可重入的。

    QObject是可重入的,但有必要记住它的三个约束条件:

    1、QObject的子对象必须在它的父对象线程中创建
     特别需要说明的是,这一约束条件意味着在次线程中创建的对象永远不能将QThread对象作为创建它们的父对象,因为QThread对象是在另外一个线程(主线程或者·另外一个不同的次线程)中创建的。

     

     2、在删除对应的QThread对象之前,必须删除所有在次线程中创建的QObject对象

     通过在QThread::run()中的堆栈上创建这些对象,就可以完成这一点。

     

     3、必须在创建QObject对象的线程中删除它们

     如果需要删除一个存在于不同线程中的QObject对象,则必须调用线程安全的QObject::deleteLater()函数,它可以置入”延期删除“(deferred delete)事件。


     由于从那些为Qt的图形用户界面支持提供编译的低级库上继承的局限性,QWidget和它的子类都是不可重入的。这样造成的后果之一就是我们不能在一个来自次线程的窗口部件上直接调用函数。打个比方说,如果想从次线程中修改一个QLabel的文本,则可以发射一个连接到QLabel::setText()的信号,或者从该线程中调用QMetaObject::invokeMethod()。例如:
 

void MyThread::run()
{
   QMetaObject::invokeMethod(label, SLOT(setText(const QString&)), Q_ARG(QString, "Hello"));                
}

 

### 回答1: Qt是一款强大的跨平台开发框架,它提供了丰富的库和工具,方便开发者进行多线程编程。下面我将以一个简单的Qt多线程并发服务器demo作为例子介绍。 首先,我们需要创建一个继承自QObject的服务器类,例如Server。在Server的构造函数中,我们可以初始化服务器的一些配置,比如监听的端口号等。 接下来,我们需要为Server类添加一个用于启动服务器的函数,例如startServer()。在该函数中,我们可以创建一个QTcpServer对象,并监听指定的端口号。当有客户端连接时,会触发新的连接信号,我们可以通过连接信号的槽函数来处理新的连接请求。 为了支持多线程,我们可以使用Qt中的QThread类,将耗时的处理逻辑放在一个独立的线程中执行。在槽函数中,我们可以创建一个新的QThread对象,并将需要处理的任务包装成一个QObject对象,通过moveToThread()函数将任务对象移动到新线程中。 如果需要在不同线程之间进行通信,我们可以使用Qt提供的信号和槽机制。在任务对象中,可以定义一些信号和槽函数,用于与其他对象进行数据交换。 在多线程环境下,为了避免资源冲突,我们需要使用互斥锁(QMutex)或者读写锁(QReadWriteLock)来对共享资源进行保护。 最后,记得在主程序中创建一个QCoreApplication对象,并运行事件循环,以便接收和处理服务器的事件和信号。 综上所述,以上便是一个简单的Qt多线程并发服务器demo的实现方式。通过合理的使用Qt提供的多线程和信号槽机制,我们可以方便地实现高效并发的服务器应用程序。当然,实际的服务器开发还需要考虑更多的细节,如线程管理、异常处理等。 ### 回答2: 在Qt中使用多线程实现并发服务器的示例可以通过以下步骤完成: 首先,我们需要创建一个继承自QThread的自定义线程类(比如ServerThread),用于处理客户端的连接请求。在这个类中,我们需要重写run()函数,在其中实现服务器的主逻辑。 在run()函数中,我们可以通过调用QTcpServer的listen()函数来监听指定的端口,同时使用connect()函数将QTcpServer的newConnection()信号与一个自定义的槽函数进行绑定。这样,当有新的客户端连接请求时,QTcpServer就会发出newConnection()信号,进而调用我们自定义的槽函数。 在自定义的槽函数中,我们可以通过调用QTcpServer的nextPendingConnection()函数获取当前连接的QTcpSocket对象,并将其移交给一个新创建的自定义线程类(比如ClientThread)进行处理。 在ClientThread类中,我们同样需要重写run()函数,在其中处理每个客户端的请求。可以通过调用QTcpSocket的read()函数来接收客户端发送的数据,并根据需求进行相应的处理。如果需要向客户端发送数据,可以使用QTcpSocket的write()函数实现。 最后,我们需要在主线程中创建一个ServerThread对象,并调用其start()函数启动服务器线程。这样,服务器就会开始监听并处理客户端的连接请求。 需要注意的是,在进行多线程编程时,需要注意线程安全的问题。比如,对于共享资源的访问需要加锁保护,以避免竞态条件的发生。 这就是一个简单的Qt多线程并发服务器的示例,通过利用多线程的特性,可以实现同时处理多个客户端的连接请求,提高服务器的并发性能。 ### 回答3: Qt是一个跨平台的应用程序开发框架,提供了丰富的多线程并发编程的支持。在Qt中,可以通过使用QThread类来创建多个线程,并通过信号与槽机制来实现线程间的通信。 创建一个Qt多线程并发服务器的demo可以采用以下步骤: 1. 创建一个继承自QThread的自定义线程类,用于处理客户端的请求。在该线程类的run()函数中,可以编写处理客户端请求的业务逻辑。 2. 在主线程中创建一个QTcpServer对象,用于监听并接收客户端的连接请求。 3. 在QTcpServer的newConnection信号的槽函数中,创建一个新的自定义线程对象,并将新连接的客户端套接字传递给该线程对象。 4. 在自定义线程类中,通过重写其构造函数,接收并保存客户端套接字。然后可以在run()函数中使用该套接字与客户端进行通信。 5. 在run()函数中,使用QTcpSocket的waitForReadyRead()函数来等待并接收客户端的请求数据。然后根据具体的业务逻辑进行处理,并通过QTcpSocket的write()函数将处理结果返回给客户端。 6. 在主线程中启动QTcpServer对象的监听功能,等待客户端的连接。 通过以上步骤,我们可以实现一个基于Qt多线程并发服务器demo,能够同时处理多个客户端的请求。每个客户端连接都会被分配一个独立的线程来处理,从而实现并发处理。使用Qt多线程并发编程的支持,能够提高服务器的性能和响应速度,同时也能简化开发和维护的工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值