qt多线程有2种方式

继承QThread的多线程

线程创建:

 1.QThread只有run函数是在新线程里的,但一般调用start函数后,会自动调用run函数,从而使线程起来。run()为虚函数。

2.如果线程已经运行,你重复调用start其实是不会进行任何处理,所以建议在start之前进行判断:使用isRunning函数。

线程退出:

1.在run函数调用exit()或者quit()函数可以结束线程,或在主线程调用terminate强制结束线程。

2.在线程运行过程调用quit、exit函数有什么效果
答案是:不会发生任何效果,QThread不会因为你调用quit或exit函数而退出正在运行到一半的run。最简单的方法是添加一个bool变量,通过主线程修改这个bool变量来进行终止,但这样有可能引起访问冲突,需要加锁。

QMutex m_lock;
QMutexLocker locker(&m_lock);    
m_isCanRun = false;

常用的函数

1.void setPriority(Priority priority);

2.Priority priority() const;

3.bool isFinished() const;

4.bool isRunning() const;

5.void exit(int retcode = 0);

6.void quit();   相当于exit(0)

7.bool wait(unsigned long time = ULONG_MAX);    //线程退出过程,需要调用wait()来阻塞等待,

8.void terminate();//使用terminate之后应该使用wait

9.void start(Priority = InheritPriority);

10.virtual void run();

11.static void sleep(unsigned long);

12.void deleteLater();

Q_SIGNALS:    
void started(QPrivateSignal);    //开始前发射
void finished(QPrivateSignal);  //结束时发射该信号
注意:
1.使用new QThread 的方式创建线程,可以使用信号finished,槽deleteLater,自动销毁分配的内存。即connect(m_printThread,&QPrintThread::finished,m_printThread,&QObject::deleteLater);

2. finished和destroyed,个人认为destroyed是在finished之后发生的。所以在new QThread 的方式,线程对象虽然销毁,但指针没有置null,要使用finished信号,自定义槽函数实现线程对象置null.即connect(m_pSaveThread,&SaveDataThread::destroyed,this,&MainWindow::SaveDataThreadDestroy)

特点
1.在线程中,也可以自定义信号,从而与主线程通信。

全局线程:

创建:

//全局线程创建时可以把窗体指针作为父对象
m_thread = new ThreadFromQThread(this);
m_thread->start();

退出:

m_thread->stopImmediately();
m_thread->wait();

//这一句是主线程等待子线程结束才能继续往下执行.(m_thread的父类是主线程窗口,主线程窗口如果没等子线程结束就destroy的话,会顺手把m_threaddelete这时就会奔溃了),因此wait的作用就是挂起,一直等到子线程结束

bool QThread::wait ( unsigned long time = ULONG_MAX ):这个函数阻塞线程直到满足下面的条件之一
1.与这个线程对象关联的线程已经结束执行(例如从run函数返回)。如果线程结束返回真值。如果线程还没有开始也返回真值
2.到达定时结束时刻。如果定时是ULONG_MAX (默认值),线程就会一直等下去(线程必须从run函数返回)。时间到,函数返回假值

局部线程:

创建:

m_printThread = new QPrintThread(NULL);//局部线程,没有父对象
m_printThread->start();

退出:

m_printThread->stopImmediately();

connect(m_printThread,&QPrintThread::finished,m_printThread,&QObject::deleteLater);//自动销毁分配的内存

connect(m_pSaveThread,&SaveDataThread::destroyed,this,&MainWindow::SaveDataThreadDestroy);  //SaveDataThreadDestroy自定义的槽函数

用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL
因此用到了QObject::destroyed信号,在线程对象析构时通知UI把m_printThread 设置为nullptr;

void MainWindow::SaveDataThreadDestroy(QObject *obj)
{  
  if(qobject_cast<QObject*>(m_pSaveThread) == obj )   
 {      
  m_pSaveThread = NULL;   
 }
}

全局线程和局部线程的区别

1.全局线程需要调wait阻塞等待,防止主线程窗口销毁了,把线程对象也释放了,而线程还没出来,会引起崩溃。

2.局部线程退出时,需要通知主线程把线程指针置NULL

继承QObject的多线程

1.用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

2.使用QObject来实现多线程比用继承QThread的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据

3.不能给它设置任何父对象.可以通过moveToThread,将它的线程相关性切换到指定线程。

全局线程:

m_pGetDataThread = new QThread();   
m_pGetDataThreadObj = new DataThreadObject();   
m_pGetDataThreadObj->moveToThread(m_pGetDataThread);
m_pGetDataThread->start();

退出

connect(m_pGetDataThread,&QThread::finished,m_pGetDataThread,&QObject::deleteLater);//看情况

connect(m_pGetDataThread,&QThread::finished,m_pGetDataThreadObj,&QObject::deleteLater);//这个函数必须使用,不然会内存泄漏

MainWindow::~MainWindow()
{  
  if(m_pGetDataThread)   
 {      
  m_pGetDataThread->quit();    
 }
}

//m_pGetDataThread 因为是通过new出来的,即在堆内创建,所以可以通过deleteLater让线程自杀。如果是直接实例化的,需要在析构时就需要调用QThread::wait(),而不是connect(m_pGetDataThread,&QThread::finished,m_pGetDataThread,&QObject::deleteLater);

connect(m_pGetDataThread,&QThread::finished,m_pGetDataThreadObj,&QObject::deleteLater);//这个函数必须使用,不然会内存泄漏

m_pGetDataThread->quit();
m_pGetDataThread->wait();

多线程的特点:

1.善用QObject::deleteLater 和 QObject::destroyed来进行内存管理 
由于多线程环境你不可预料下一步是哪个语句执行,因此,加锁和自动删除是很有用的工具,加锁是通过效率换取安全,用Qt的信号槽系统可以更有效的处理这些问题。

2.如果线程要用到消息循环,使用继承QObject的多线程方法更简单
3.继承QObject的多线程不能指定父对象
4.把所有耗时操作都作为槽函数

5.QMutex会带来一定的耗时,大概速度会降低1.5倍(Release模式)

6.子对象不能脱离父对象,单独切换到与父对象不同的线程中

7. QObject的子对象必须在创建其parent的线程中创建。即QObject的线程相关性默认会和它的parent保持一致

8.在Qt中,当一个对象被移到另一个线程时,他的所有子对象也会一并转移到另外那个线程。

9.在qt中 线程创建的对象必须在线程中释放。

10.在qt中 io类不能跨线程调用Qobject。可通过信号槽的方式

其他:

线程同步问题  跟linux的思想一模一样,只是函数名不同而已。

1.QMutex   

2.QMutexLocker    是对QMutex的简化,直接使用构造函数和析构函数的方式,而不是lock和unlock的调用

缺点:每次只有一个线程获取到互斥量的权限。

3. QReadWriteLock 读写锁

特点:可以多个线程以只读方式访问,但只有一个线程以写的方式访问。

LockForRead();
LockForWrite();
unlock();

另外,QReadlocker 和QWriteLocker是QReadWriteLock的简化,如同QMutexLocker  是QMutex   的简化一样。无须使用unlock().

4. QWaitCondition

bool wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX);  //解锁互斥量,并阻塞等待唤醒,唤醒后由锁定互斥量
 bool wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = ULONG_MAX);
 void wakeOne(); //唤醒一个等待线程,具体哪一个线程,由操作系统决定
 void wakeAll();//唤醒所有等待线程

用于生产者-消费者,比互斥量效率更高

5. QSemaphore 信号量   

比互斥量更高级,互斥量只能锁定一次,信号量可以多次使用,每次创建时可以设定初始值 void acquire(int n = 1);  //获取n个资源

bool tryAcquire(int n = 1); 
bool tryAcquire(int n, int timeout);
void release(int n = 1);  //释放资源