QTcpServer运行在子线程


创建一个QThread和QWorker(继承自QObject)类对象,使用moveToThread函数移动到thread中运行,通过thread类start信号和worker的init槽函数绑定,init槽函数内是一些初始化操作,然后定义个定时器,周期触发doWork()。

网上有很多教程是在doWork()中使用while(isRunning)死循环的形式,不建议这么干,如果线程一直在doWork中死循环,那么他是无法接收到来自外部的信号的。推荐的方法是用定时器周期触发。

workserver.h

#ifndef WORKSERVER_H
#define WORKSERVER_H

#include <QObject>
#include <QThread>
#include <QTcpServer>
#include <QTcpSocket>
#include <QByteArray>
#include <QDebug>


class WorkServer : public QObject
{
    Q_OBJECT
public:
    explicit WorkServer(QObject *parent = nullptr);
    ~WorkServer();

signals:
    void sigRecv(QByteArray ba, quint32 cnt);

private:
    QTcpServer* m_server;

};

#endif // WORKSERVER_H

workserver.cpp

#include "workserver.h"

WorkServer::WorkServer(QObject *parent) : QObject(parent)
{
    static quint32 count = 0;
    qDebug()<<"WorkServer thread ID:"<<QThread::currentThreadId();
    m_server = new QTcpServer(this);
    m_server->listen(QHostAddress::Any, 8888);
    connect(m_server, &QTcpServer::newConnection, this, [=](){
        QTcpSocket* tcpsock = m_server->nextPendingConnection();
        connect(tcpsock, &QTcpSocket::readyRead, this, [=](){
            QByteArray ba = tcpsock->readAll();
            count++;
            emit sigRecv(ba, count);
            tcpsock->write("5566");
            //            qDebug()<<"tcpsock recv:"<<ba;
            //            qDebug()<<"WorkServer thread ID:"<<QThread::currentThreadId();
        });
        connect(tcpsock, &QTcpSocket::disconnected, this, [=](){
            qDebug()<<"tcpsock disconnected...";
            tcpsock->deleteLater();
            qDebug()<<"WorkServer thread ID:"<<QThread::currentThreadId();
            count = 0;
        });
    });

}

WorkServer::~WorkServer()
{
//    m_server->deleteLater();
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>
#include "workserver.h"


QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void clientSend(QByteArray ba, quint32 cnt);

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QThread* m_threadServer;
    WorkServer* m_workServer;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug()<<"main thread ID:"<<QThread::currentThreadId();
    m_threadServer = new QThread; //不要指定parent
    m_workServer = new WorkServer; //不要指定parent
    connect(m_workServer, &WorkServer::sigRecv, this, &Widget::clientSend);
    connect(m_threadServer, &QThread::finished, this, [=](){
        m_threadServer->quit();
        m_threadServer->deleteLater();
    });
    m_workServer->moveToThread(m_threadServer);
    m_threadServer->start();

}

Widget::~Widget()
{
//    m_threadServer->quit();
    m_workServer->deleteLater();
    delete ui;
}

void Widget::clientSend(QByteArray ba, quint32 cnt)
{
    QString str(ba);
    QString str2 = QString::number(cnt);
    str += " cnt: " + str2;
    ui->te_Recv->append(str);
}


void Widget::on_pushButton_clicked()
{
    ui->te_Recv->clear();
}


运行结果

在这里插入图片描述

两种方法的比较

两种方法来执行线程都可以,随便你的喜欢。不过看起来第二种更加简单,容易让人理解。不过我们的兴趣在于这两种使用方法到底有什么区别?其最大的区别在于:

  • moveToThread方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个的槽函数,再建立触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调用moveToThread方法交给一个QThread对象,再调用QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执行某个任务,只需要发出对应的信号就可以。其优点是我们可以在一个worker类中定义很多个需要做的工作,然后发出触发的信号线程就可以执行。相比于子类化的QThread只能执行run()函数中的任务,moveToThread的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。

  • 子类化QThread的方法,就是重写了QThread中的run()函数,在run()函数中定义了需要的工作。这样的结果是,我们自定义的子线程调用start()函数后,便开始执行run()函数。如果在自定义的线程类中定义相关槽函数,那么这些槽函数不会由子类化的QThread自身事件循环所执行,而是由该子线程的拥有者所在线程(一般都是主线程)来执行。如果你不明白的话,请看,第二个例子中,子类化的线程的槽函数中输出当前线程的ID,而这个ID居然是主线程的ID!!事实的确是如此,子类化的QThread只能执行run()函数中的任务直到run()函数退出,而它的槽函数根本不会被自己的线程执行。

同步QThread的类

为了同步线程,Qt提供了QMutex、QReadWriteLock、QSemaphore和QWaitCondition类。主线程等待与其他线程的中断时,必须进行同步。例如:两个线程同时访问共享变量,那么可能得不到预想的结果。因此,两个线程访问共享变量时,必须进行同步。

  1. 一个线程访问指定的共享变量时,为了禁止其他线程访问,QMutex提供了类似锁定装置的功能。互斥体激活状态下,线程不能同时访问共享变量,必须在先访问的线程完成访问后,其他线程才可以继续访问。
  2. 一个线程访问互斥体锁定的共享变量期间,如果其他线程也访问此共享变量,那么该线程将会一直处于休眠状态,直到正在访问的线程结束访问。这称为线程安全。
  3. QReadWriteLock和QMutex的功能相同,区别在于,QReadWriteLock对数据的访问分为读访问和写访问。很多线程频繁访问共享变量时,与QMetex相对,使用QReadWriteLock更合适。
  4. QSemaphore拥有和QMutex一样的同步功能,可以管理多个按数字识别的资源。QMutex只能管理一个资源,但如果使用QSemaphore,则可以管理多个按号码识别的资源。
  5. 条件符合时,QWaitCondition允许唤醒线程。例如,多个线程中某个线程被阻塞时,通过QWaitCondition提供的函数wakeOne()和wakeAll()可以唤醒该线程。

可重入性与线程安全

  • 可重入性:两个以上线程并行访问时,即使不按照调用顺序重叠运行代码,也必须保证结果;
  • 线程安全:线程并行运行的情况下,虽然保证可以使程序正常运行,但访问静态空间或共享(堆等内存对象)对象时,要使用互斥体等机制保证结果。

一个线程安全的函数不一定是可重入的;一个可重入的函数缺也不一定是线程安全的!

可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个线程调用此函数时,很有可能使有关全局变量变为不可知状态。

满足下列条件的函数多数是不可重入的:

  • 函数体内使用了静态的数据结构和全局变量,若必须访问全局变量,利用互斥信号量来保护全局变量;
  • 函数体内调用了malloc()或者free()函数;
  • 函数体内调用了标准I/O函数。

常见的不可重入函数有:

  • printf --------引用全局变量stdout
  • malloc --------全局内存分配表
  • free --------全局内存分配表

也就是说:本质上,可重入性与C++类或者没有全局静态变量的函数相似,由于只能访问自身所有的数据变量区域,所以即使有两个以上线程访问,也可以保证安全性。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值