目录
1、槽函数的调用位置
QThread类拥有发射信号和定义槽函数的能力
-void started() ,线程开始运行时发射该信号
-void finished() ,线程完成运行时发射该信号
-void terminated() ,线程被异常终止时发射该信号
发现问题 TestThread.h
#ifndef TESTTHREAD_H
#define TESTTHREAD_H
#include <QThread>
class TestThread : public QThread
{
Q_OBJECT
protected:
void run();
public:
explicit TestThread(QObject *parent = 0);
signals:
void testSignal();
protected slots:
void testSlot(); // 在线程类定义的槽函数,执行时会在哪里执行呢?子线程中?主线程中?
};
#endif // TESTTHREAD_H
TestThread.cpp
#include "TestThread.h"
#include <QDebug>
TestThread::TestThread(QObject *parent) :
QThread(parent)
{
connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}
void TestThread::run()
{
qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();
for(int i=0; i<5; i++)
{
qDebug() << "void TestThread::run() i = " << i;
sleep(1);
}
emit testSignal(); // 子线程发送的信号, 发往哪里了呢?
qDebug() << "void TestThread::run() -- end";
}
void TestThread::testSlot()
{
qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}
MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject
{
Q_OBJECT
public:
explicit MyObject(QObject *parent = 0);
signals:
protected slots:
void getStarted();
void getFinished();
void getTerminated();
};
#endif // MYOBJECT_H
MyObject.cpp
#include "MyObject.h"
#include <QThread>
#include <QDebug>
MyObject::MyObject(QObject *parent) :
QObject(parent)
{
}
void MyObject::getStarted()
{
qDebug() << "void MyObject::getStarted() tid = " << QThread::currentThreadId();
}
void MyObject::getFinished()
{
qDebug() << "void MyObject::getFinished() tid = " << QThread::currentThreadId();
}
void MyObject::getTerminated()
{
qDebug() << "void MyObject::getTerminated() tid = " << QThread::currentThreadId();
}
main.cpp
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main() tid = " << QThread::currentThreadId();
TestThread t;
MyObject m;
QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()));
QObject::connect(&t, SIGNAL(terminated()), &m, SLOT(getTerminated()));
t.start();
return a.exec();
}
子线程类定义的槽函数为什么没在本线程内被调用执行?
2、线程对象的依附性
默认情况下,对象依附于自身被创建的线程;例如:对象在主线程( main()函数 )中被创建,则依附于主线程。
默认情况下,槽函数在其所依附的线程中被调用执行!
QObject::moveToThread 用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行
int main(int argc, char *argv[])
{
//...
TestThread t; // t依附于主线程,因此t中的槽函数在主线程中被调用执行
MyObject m; // m依附于主线程,因此m中的槽函数在主线程中被调用执行
/* 改变m的线程依附性,使其依附于线程t,那么m中槽函数将在t中执行 */
m.moveToThread(&t);
// t.moveToThread(&t); // t的槽函数在自己的线程中被调用
//...
}
仅仅这样,m中的槽函数还无法在线程t中执行,因为线程t没有开启事件循环,收到的事件无法处理
前提条件 :对象依附的线程需要开启了事件循环
后置结果 :对象中的槽函数在依附的线程中被调用执行
3、线程中的事件循环
信号与槽的机制需要事件循环的支持,QThread类中提供的exec()函数用于开启线程的事件循环,只有事件循环开启,槽函数才能在信号发送后被调用
-主线程开启了事件循环,所以槽函数能在主线程被调用;t 线程没有开启事件循环,所以槽函数不能在t线程调用 ;即要槽函数在指定的线程中调用,就需要在指定的线程中调用exec()开启事件循环
-无论事件循环是否开启,信号发送后会直接进入对象所依附线程的事件队列;然而,只有开启了事件循环,对应的槽函数才会在线程中被调用。
在TestThread中开启t的事件循环
void TestThread::run()
{
qDebug() << "void TestThread::run() -- begin tid = " << currentThreadId();
for(int i=0; i<5; i++)
{
qDebug() << "void TestThread::run() i = " << i;
sleep(1);
}
emit testSignal();
exec(); // 开启线程t的事件循环
qDebug() << "void TestThread::run() -- end";
}
可以发现没有打印线程结束语句(end)
研究槽函数的具体执行线程有什么意义?
当信号的发送与对应槽函数的执行在不同线程中时,可能产生临界资源的竞争问题!
例如:示例中的线程类中某个成员变量在本线程中改变,但他的槽函数却在其它线程同时调用,同时修改这一变量
如果线程体函数中开启了事件循环, 线程如何正常结束?
QThread::exec()使得线程进入事件循环 ,事件循环结束前,exec()后的语句无法执行
quit() 和 exit()函数用于结束事件循环 ,quit() ↔ exit(0) , exec()的返回值由exit()参数决定
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"
int main(int argc, char *argv[])
{
// ...
t.start();
t.wait(8 * 1000); // 主线程等待t线程执行结束才向下运行,最多等待8s
t.quit(); // t线程退出事件循环
return a.exec();
}
什么时候需要在线程中开启事件循环?
线程事件循环的应用
-事务性操作(间断性IO操作,等) 可以开启线程的事件循环;每次操作通过发送信号的方式使得槽函数在子线程中执行
概念小科普-文件缓冲区
-默认情况下,文件操作时会开辟一段内存作为缓冲区 ,向文件中写入的数据会先进入缓冲区 ,只有当缓冲区满或者遇见换行符才将数据写入磁盘
-缓冲区的意义在于,减少磁盘的低级IO操作,提高文件读写效率!
FileWriter.h
#ifndef FILEWRITER_H
#define FILEWRITER_H
#include <QObject>
#include <QFile>
#include <QThread>
class FileWriter : public QObject
{
Q_OBJECT
class Worker : public QThread
{
protected:
void run();
};
QFile m_file;
Worker m_worker;
public:
explicit FileWriter(QString file, QObject *parent = 0);
bool open();
void write(QString text);
void close();
~FileWriter();
signals:
void doWrite(QString text);
void doClose();
protected slots:
void writeSlot(QString text);
void closeSlot();
};
#endif // FILEWRITER_H
FileWriter.cpp
#include "FileWriter.h"
#include <QDebug>
void FileWriter::Worker::run()
{
qDebug() << "void FileWriter::Worker::run() - begin: tid = " << currentThreadId();
exec();
qDebug() << "void FileWriter::Worker::run() - end";
}
FileWriter::FileWriter(QString file, QObject *parent) :
QObject(parent), m_file(file)
{
connect(this, SIGNAL(doWrite(QString)), this, SLOT(writeSlot(QString)));
connect(this, SIGNAL(doClose()), this, SLOT(closeSlot()));
moveToThread(&m_worker); //当前类槽函数都在内部线程执行
m_worker.start();
}
bool FileWriter::open()
{
return m_file.open(QIODevice::WriteOnly | QIODevice::Text);
}
void FileWriter::write(QString text)
{
qDebug() << "void FileWriter::write(QString text) tid = " << QThread::currentThreadId();
emit doWrite(text); // 信号由内部线程处理
}
void FileWriter::close()
{
qDebug() << "void FileWriter::close() tid = " << QThread::currentThreadId();
emit doClose();
}
// 槽函数将在内部线程中执行
void FileWriter::writeSlot(QString text)
{
qDebug() << "void FileWriter::writeSlot(QString text) tid = " << QThread::currentThreadId();
m_file.write(text.toAscii());
m_file.flush();
}
void FileWriter::closeSlot()//关闭文件也是事务性操作
{
qDebug() << "void FileWriter::closeSlot() tid = " << QThread::currentThreadId();
m_file.close();
}
FileWriter::~FileWriter()
{
m_worker.quit();
}
main.cpp
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include "FileWriter.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main() tid = " << QThread::currentThreadId();
FileWriter writer("C:/Users/wss/Desktop/test.txt");
if( writer.open() )
{
writer.write("1\r\n"); //三次事务性操作,通过开启子线程完成,耗时在子线程上
writer.write("2\r\n");
writer.write("3\r\n");
writer.close();
}
return a.exec();
}
Qt线程的使用模式
-无事件循环模式
★ 后台执行长时间的耗时任务 :文件复制,网络数据读取,等
-开启事件循环模式
★ 执行事务性操作 :文件写入,数据库写入,等