QT线程类相关

线程相关

线程类

Qt线程三种使用:

QThread
a.继承QThread

重写QThread的run()函数,由于QThread继承QObject,故可以通过信号槽方式与主线程通信,再调用start启动线程。

QObject的moveToThread

void moveToThread(QThread *thread);将QObject类任务交给QThread来访问。

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
private:
    Ui::Widget *ui;
};

class CalHelper : public QObject
{
    Q_OBJECT
public:
   explicit CalHelper(QObject *parent);
    ~CalHelper();

public slots:
    void slotHandTimeOut();

private:
     QTimer m_task_timer;
};

//cpp
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    QThread *thread = new QThread(this);
    qDebug()<<"m_Thread parent"<<thread->parent();
    CalHelper *task = new CalHelper(nullptr);//必须为空,有父对象的无法移动
    QThread::sleep(1);
    task->moveToThread(thread);
    connect(thread, &QThread::finished, task, &QObject::deleteLater);
    thread->start();
}

Widget::~Widget()
{
    delete ui;
}

CalHelper::CalHelper(QObject *parent):QObject(parent)
{
    qDebug()<<"timeout  currentThreadId:"<<QThread::currentThreadId();
    m_task_timer.start(30);
    connect(&m_task_timer,&QTimer::timeout,this,&CalHelper::slotHandTimeOut);
}

CalHelper::~CalHelper()
{
    qDebug()<<"CalHelper is delete";
}

void CalHelper::slotHandTimeOut()
{
    qDebug()<<"timeout  currentThreadId:"<<QThread::currentThreadId();
    qDebug()<<"CalHelper parent"<<parent();
}

输出为:

main currentThreadId: 0x4f7c
thread parent Widget(0x74fdec, name = "Widget")
timeout  currentThreadId: 0x4f7c
timeout  currentThreadId: 0x1d80
CalHelper parent QObject(0x0)
timeout  currentThreadId: 0x1d80
CalHelper parent QObject(0x0)

可见,未移动前,超时槽触发是在主线程,移动后,超时槽触发在子线程。moveToThread使得执行流在子线程执行。
使用moveToThread注意:

  1. 构造成功Object后,通过moveToThread()将Object对象移到到新线程中,如此一来obj执行流都将在子线程中运行,**但被移动obj即task的控制权仍然属于主线程。**movetoThread()的作用是将槽函数在指定的线程中调用。仅有槽函数在指定线程中调用,包括构造函数都仍然在主线程中调用!!!
  2. CalHelper须继承自顶层父类 Object,且构造时不能指定父对象,否则不能移动。
  3. 如果 Thread为nullptr,则该对象及其子对象的所有事件处理都将停止,因为它们不再与任何线程关联。
  4. 调用movetoThread() 时,移动对象的所有计时器将被重置。 计时器首先在当前线程中停止,然后在targetThread中重新启动(以相同的间隔),这时定时器属于子线程。若在线程之间不断移动对象可能会无限期地延迟计时器事件。
    a适合高频任务;b适合单次任务。通过队列连接方式,使得在子线程执行,触发一次,执行一次。
    常见接口:QThread *currentThread();//返回线程指针
    Qt::HANDLE currentThreadId();//返回线程id。
    1、当QObject接收到信号或发布的事件时,槽函数或事件处理程序将在对象所在的线程中运行。(如果QObject没有线程关联(即如果thread()返回nullptr),或者如果它位于没有运行事件循环的线程中,则它无法接收信号或发布的事件)。
    2、默认情况下,QObject存在于创建它的线程中。可以使用thread()查询对象的线程关联,并使用moveToThread()更改对象的线程关联。
    3、所有QObject必须与其父对象位于同一线程中。因此:如果所涉及的两个QObject位于不同的线程中,setParent()将失败。当一个QObject对象被移动到另一个线程时,该对象的所有子线程也将被自动移动。如果QObject对象有父对象,moveToThread()将失败。如果QObject对象是在QThread::run()中创建的,则它们不能成为QThread对象的子对象,因为QThread对象不在调用QThread::run()的线程中。注意:QObject的成员变量不会自动成为其子变量。必须通过向子构造函数传递指针或调用setParent()来设置父子关系。如果没有此步骤,调用moveToThread()时,对象的成员变量将保留在旧线程中。
    无复制构造函数和赋值运算符
    QObject既没有复制构造函数,也没有赋值运算符。它们是在宏Q_DISABLE_COPY()中设置了禁止生成。实际上,所有从QObject派生的Qt类(直接或间接)都使用这个宏来声明它们的复制构造函数和赋值操作符是私有的。

这带来的结果是应该使用指向QObject(或者指向您的QObject子类)的指针。例如,如果没有复制构造函数,就不能使用QObject的子类作为存储在某个容器类中的值,必须存储指针。

QThreadPool + QRunnable

QRunnable类同QThread类似,需要重写run函数,且必须要借助于线程池启动线程,但由于不是继承QObject,无法使用信号槽与外界通信,如果要使用QRunnablel与外界通信,则需要:
1. 使用多重继承(有一个必须继承QObject);
2. 使用QMetaObject::invokeMethod(需要维护和外界通信的QObject对象成员)。

 	m_pRunnable = new CusRunnable(this);
    QThreadPool	  threadpool;
    threadpool.setMaxThreadCount(1);//设置本地线程池最大为1.
    threadpool.start(m_pRunnable);
    //QThreadPool::globalInstance()->start(m_pRunnable);// 或使用全局线程池。
// 重新run,并与m_pObj对象通信
void CusRunnable::run()
{
    qDebug() << __FUNCTION__ <<"other Thread:"<< QThread::currentThreadId();
    //实现与主线程通信, m_pObj对象有一个:Q_INVOKABLE void setText(QString msg);
    QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"this is AA!"));
    QThread::msleep(1000);
} 

既然QThread这么简单我们为什么还要使用QThreadPool + QRunnable创建线程池的方法来使用线程机制呢?
主要原因:当线程任务量非常大的时候,每个任务都用一个QThread来执行,会占用全局线程池量,同时会频繁创建和释放QThread对象,带来比较大的内存开销,而线程池则可以有效避免该问题。创建的指定的线程个数,直到任务完成后才释放(线程id一直不变)

#include <QObject>  
#include <QRunnable>  
#include <QThread>  
#include <QThreadPool> 
#include <QDebug>  

class HelloWorldTask : public QRunnable 
{  
     // 线程执行任务:每间隔1s打印出线程的信息 
    void run() 
    {  
        int m_dataMem[256*1000];                  // 占约 1MB内存空间   
        for (int nCount = 0; nCount < 5; nCount++) 
        {  
        	qDebug() << QThread::currentThread();  
        	QThread::msleep(1000);  
        }  
    }  
};  

int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv); 
	QThreadPool   threadpool;   
	threadpool.setMaxThreadCount(3);   
    for (int nNum = 0; nNum < 100; nNum++)  
	{  
    	threadpool.start(new HelloWorldTask);   //可被多个不同任务替换。,但都交给线程池完成。
    	QThread::msleep(1000);  
	}  
	return a.exec();  
}  

通过线程池,实现多个任务由三个线程完成(只有所有任务结束之后,三个线程才终止并释放资源。)

QtConcurrent

QtConcurrent 是一个命名空间,主要为多线程服务,它提供了高层次的函数接口 (APIs),使所写程序,可根据计算机的 CPU 核数,自动调整运行的线程数目。会在一个单独的线程中执行,并且该线程取自全局QThreadPool,该函数的返回值通过QFuture API提供。

Qt Concurrent已经从 QtCore 中移除并成为了一个独立的模块。使用时,需要添加:

Qt += concurrent

几种用法:

QtConcurrent::run(Function function, ) 等价于 QtConcurrent::run(QThreadPool::globalInstance(), function, …);

QtConcurrent :: run()也接受指向成员函数的指针。第一个参数必须是一个const引用或一个指向该类实例的指针

使用Lambda函数

QFuture<void> future = QtConcurrent::run([](=) {

  // Code in this block will run in another thread }
);

使用成员函数

extern void myFunc(QString &str); 
QFuture<void> f2 =QtConcurrent::run(this,&Widget::myFunc,QString("aaa"));

调用外部函数

QFuture<void> f1 =QtConcurrent::run(func,QString(index++));
f1.waitForFinished();

相对于movetoThread(),他的好处就相当于是创建一个线程,用来跑指定对象的某个任务(或者全局的某个任务)。

线程执行后,需要进行wait()进行阻塞,否则,线程退出会报错:QThread: Destroyed while thread is still running

与线程相关的connect连接类型

Qt::AutoConnection
默认连接类型,如果信号接收方与发送方在同一个线程,则使用 Qt::DirectConnection,否则使用 Qt::QueuedConnection;连接类型在信号 发射时决定。

Qt::DirectConnection
信号所连接的槽函数将会被立即执行,并且是在发射信号的线程;倘若槽函数执行的是耗时操作、信号由UI线程发射,则会阻塞Qt的事件循环,UI会进入无响应状态 。

Qt::QueuedConnection
槽函数将会在接收者的线程被执行,此种连接类型下的信号倘若被多次触发、相应的槽函数会在接收者的线程里被顺次执行相应次数;当使用 QueuedConnection 时,参数类型必须是Qt基本类型,或者使用 qRegisterMetaType() 进行注册了的自定义类型。

如使用信号:void sigOpenBuyPage(FFBuyPageType type);需要在构造函数时,进行注册: qRegisterMetaType(“FFBuyPageType”);

Qt::BlockingQueuedConnection
和 Qt::QueuedConnection 类似,区别在于发送信号的线程在槽函数执行完毕之前一直处于阻塞状态;收发双方必须不在同一线程,否则会导致死锁 。

如,当信号在本类实现,并阻塞式连接,将发生死锁。故信号槽必须禁止阻塞连接。

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void slotHandTimeOut();
    
private:
    Ui::Widget *ui;
    QTimer m_task_timer;

};

//cpp
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);   
    m_task_timer.start(1000);
    connect(&m_task_timer,&QTimer::timeout,this,&Widget::slotHandTimeOut,Qt::BlockingQueuedConnection);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::slotHandTimeOut()
{
    qDebug()<<"timeout  currentThreadId:"<<QThread::currentThreadId();
    qDebug()<<__FILE__<<"parent"<<parent();
}

Qt::UniqueConnection
执行方式与 AutoConnection 相同,不过关联是唯一的。(如果相同两个对象,相同的信号关联到相同的槽,那么第二次 connect 将失败

互斥锁:QMutex

QMutex,任意时刻至多有一个线程可以使用该锁,若一个线程尝试获取 mutex ,而此时 mutex 已被锁住。则这儿线程将休眠直到 mutex解锁 为止。互斥锁经常用于共享数据。

tryLock();//非阻塞
lock();//阻塞
unlock();

例:
QMutex mute;
mute.lock()
mute.unlock();

QMutexLocker这个类是基于QMutex的便利类,这个类不能够定义私有成员变量和全局变量,只能够定义局部变量来使用

如果需要对一个全局变量区域进行保护,那么QMutex定义的变量就得是全局的,如果是对某个类作用域进行保护,则QMutex可以定义成员变量。

如:

void 类名::deleteList()
{
	QMutexLocker locker(&m_Mtx);
	for(auto media:m_list)
    {
        delete media;
    }
}

QMutex mutex;
void thread1()
{
    mutex.lock();
    //dosomething()
    mutex.unlock();
}

void thread2()
{
    mutex.lock();
    //dosomething()
    mutex.unlock();
}

QMutexLocker上锁,解锁的原理:

在该局部变量被创建的时候上锁,当所在函数运行完毕后该QMutexLocker局部变量在栈中销毁掉,根据他自己的机制也就相对应的解锁了。注意,如果该局部变量在中间被打断,那么QMutexLocker上的锁就不会被解锁掉,因为该函数没有被完整的是执行完。QMutexLocker所创建的局部变量也没有被正确销毁销毁,可能就和QMutexLocker他自己本身的机制不服也就不会解锁。

读写锁QReadWriteLock

QReadWriteLock,与 QMutex 类似,不过它允许多个线程对共享数据进行读取,写独占。使用它替代 QMutex 可提高多线程程序的并发度。

void lockForRead()
void lockForWrite()
void unlock()

读写锁的使用场景:在一些共享资源的读和写操作,且写操作没有读操作那么频繁的场景下可以用读写锁。

QReadWriteLock lock;
void ReaderThread::run()
{
    ...
    lock.lockForRead();
    read_file();
    lock.unlock();
    ...
}

void WriterThread::run()
{
    ...
    lock.lockForWrite();
    write_file();
    lock.unlock();
    ...
}

QReadLocker/QWriteLocker

与QMutexLocker相似,在构造时lock(),在析构时unlock();

信号量QSemaphore

QSemaphoreQMutex 的一般化,用于保护一定数量的相同的资源。典型的是 生成者-消费者

void acquire(int n = 1)
int available() const
void release(int n = 1)//可用于新建信号量资源
bool tryAcquire(int n = 1)
bool tryAcquire(int n, int timeout)
QSemaphore sem(5);      // sem.available() == 5
sem.acquire(3);         // sem.available() == 2
sem.acquire(2);         // sem.available() == 0
sem.release(5);         // sem.available() == 5
sem.release(5);         // sem.available() == 10
sem.tryAcquire(1);      // sem.available() == 9, returns true
sem.tryAcquire(250);    // sem.available() == 9, returns false

条件变量 QWaitCondition

WaitCondition 用于多线程的同步,一个线程调用QWaitCondition::wait()阻塞等待,直到另一个线程调用QWaitCondition::wake()唤醒才继续往下执行。

wait() 函数必须传入一个已上锁的 mutex 对象,等待过程中,mutex会解锁

示例:生成者与消费者

//不严谨用法:
// 示例一
// 主线程
Send(&packet);
mutex.lock();
condition.wait(&mutex); 
if (m_receivedPacket)
{
    HandlePacket(m_receivedPacket); // 另一线程传来回包
}
mutex.unlock();
// 通信线程
m_receivedPacket = ParsePacket(buffer);  // 将接收的数据解析成包
mutex.lock();
condition.wakeAll();
mutex.unlock();

主线程中,调用 Send(&packet) 发送后,假如通信线程立即收到回包,在主线程还来不及调用 wait() 的时候,已经先 wakeAll() 了,显然这次唤醒是无效的,但主线程继续调用wait(),然后一直阻塞在那里。

//通过条件变量,实现
//严谨用法
// 示例二
// 主线程
mutex.lock();
Send(&packet);//作为原子操作
condition.wait(&mutex); 
if (m_receivedPacket)
{
    HandlePacket(m_receivedPacket); // 另一线程传来回包
}
mutex.unlock();

// 通信线程
m_receivedPacket = ParsePacket(buffer);  // 将接收的数据解析成包
mutex.lock();
condition.wakeAll();
mutex.unlock();

//源码

//qthread.cpp
QThread::QThread(QObject *parent): QObject(*(new QThreadPrivate), parent);
QThread::QThread(QThreadPrivate &dd, QObject *parent): QObject(dd, parent);
QThread::~QThread();
bool QThread::isFinished() const;
bool QThread::isRunning() const;
void QThread::setStackSize(uint stackSize);
int QThread::exec();
void QThread::exit(int returnCode);
void QThread::quit();
void QThread::run();
void QThread::setPriority(Priority priority);
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值