第十九章 进程和线程——Qt

一、进程

1.运行一个进程

Qt的QProcess类用来启动一个外部程序并与其进行通信。
要启动一个进程,可以使用start()函数,然后将程序名称和运行这个程序所要使用的命令行参数作为该函数的参数。
执行完start()函数后,QProcess进入Starting状态,当程序已经运行后,QProcess就会进入Running状态并发射started()信号。
当进程退出后,QProcess重新进入NotRunning状态(初始状态)并发射finished()信号。
任何时间发生了错误,QProcess都会发射error()信号,也可以调用error()来查看错误的类型和上次发生的错误。使用state()可以查看当前进程的状态。

2.进程间通信

Qt提供了多种方法在Qt应用程序中实现进程间通信IPC(Inter-Process Communication)。
有TCP/IP;共享内存;D-Bus;QProcess;会话管理等。(这里主要讲述共享内存)
qt实现共享内存的步骤如下:

  • 向共享内存中提供数据的一方:
    • 定义QSharedMemory shareMemory,并设置标志名shareMemory.setKey();
    • 将共享内存与主进程分离 shareMemory.detach();
    • 创建共享内存 shareMemory.create();
    • 将共享内存上锁shareMemory.lock();
    • 将进程中要共享的数据拷贝到共享内存中;
    • 将共享内存解锁shareMemory.unlock();
  • 从共享内存中取数据的一方:
    • 定义QSharedMemory shareMemory,并设置与数据提供方一样的标志名shareMemory.setKey();
    • 将共享内存上锁shareMemory.lock();
    • 将共享内存与主进程绑定shareMemory.attach(),使该进程可以访问共享内存的数据;
    • 从共享内存中取数据;
    • 使用完后将共享内存解锁shareMemory.unlock(),另外将共享内存与该进程分离shareMemory.detach();
3.QSharedMemory类常用的操作
(1)QSharedMemory类对象的创建

利用QSharedMemory类创建实例对象时,必须为该共享内存指定关键字(即为该共享内存起一个名字)。
只有当共享内存被设置了关键字之后,才可以执行创建create()、关联attach()等操作。为共享内存指定关键字有两种方法:

  • 通过构造函数QSharedMemory::QSharedMemory ( const QString & key, QObject * parent =0 )为实例对象传入关键字;
例如:
SharedMemory* sharememory;
sharememory = newQSharedMemory("QSharedMemoryExample");
  • 通过构造函数QSharedMemory::QSharedMemory (QObject * parent = 0 )构造实例对象,之后调用setKey()函数为该实例对象设置关键字。
例如:
QSharedMemory* sharememory;						
sharememory = new QSharedMemory();						
sharememory->setKey("QSharedMemoryExample ");		
(2)创建共享内存
bool QSharedMemory::create ( int size, AccessMode mode =ReadWrite )

为QSharedMemory类实例对象创建一个空间大小为size的共享内存,该内存空间默认的访问方式为可读可写。
共享内存创建成功返回true,否则返回false。
QSharedMemory类定义一个枚举类变量AccessMode,指定了两种共享内存的访问方式:

  • QSharedMemory::ReadOnly 只读方式访问共享内存
  • QSharedMemory::ReadWrite 读写方式访问共享内存
(3)关联共享内存
bool QSharedMemory::attach ( AccessMode mode =ReadWrite )

将以关键字key命名的共享内存和当前程序进行关联,共享内存默认的访问方式为可读可写。
如果程序和共享内存关联成功,返回true,否则返回false。

(4)分离共享内存
bool QSharedMemory::detach ()

解除共享内存和程序的关联,即调用该函数后,程序不可以再访问共享内存。如果该共享内存被多个程序实例所关联, 当最后一个程序实例和共享内存解除关联后,该共享内存将由操作系统自动释放掉。分离操作成功,返回true。
如果返回false,通常意味着该共享内存和程序分离失败,或者其他程序当前正在访问该共享内存,分离操作执行失败。

(5)判断共享内存的关联状态
bool QSharedMemory::isAttached ()const

该函数用来判断程序(调用该函数的程序)是否和共享内存进行关联,是返回true,否返回false。

(6)设置/获取共享内存的关键字
QString QSharedMemory::key ()const    //获取共享内存关键字

Qt应用程序通过关键字来辨识共享内存。key ()函数用来获取共享内存的关键字,如果没有指定实例对象的关键字,或者共享内存的关键字是由nativeKey ()函数指定的话,则返回空。

void QSharedMemory::setKey (const QString & key )        //设定共享内存关键字

setKey ()函数用来为共享内存段设定关键字(为共享内存命名),如果参数key的值和构造函数或者之前指定的关键字相同的话,则该函数将不做任何操作,直接返回。

(7)锁定/解锁共享内存
bool QSharedMemory::lock ()                    //锁定共享内存

如果共享内存资源当前处于释放状态,进程调用该函数将共享内存中的资源锁定,并返回true。其他进程将不能访问该共享内存。
如果共享内存被其他进程占用时,则该函数会一直处于阻塞状态,直到其他进程使用完毕,释放共享内存资源。

bool QSharedMemory::unlock ()         //解锁共享内存

如果共享内存资源被当前进程所占有,调用该函数将解锁该共享资源,并返回true。
如果当前进程没有占用该资源,或者共享内存被其他进程访问,则不做任何操作并返回false。
为了保证共享内存中数据的完整性,当一个进程在读写共享内存的时候,其他进程不允许对该共享区域进行访问。
QSharedMemory类提供了lock()函数和unlock()函数来实现这一共享内存访问机制。
某一程序对共享内存进行读写操作之前,需要调用lock()函数锁定该共享内存,之后独享共享内存中的数据,并对数据进行读写等操作。
共享内存访问完毕,调用unlock()函数,释放共享内存的使用权限。

(8)错误原因
  • SharedMemoryError QSharedMemory::error ()const
    当共享内存出错时,调用该函数显示相应的错误代码。

  • QString QSharedMemory::errorString ()const
    当共享内存出错时,调用该函数,以文本形式显示错误原因。

(9)获取共享内存的地址
const void *QSharedMemory::constData ()const
void * QSharedMemory::data ()
			
const void *QSharedMemory::data ()const          //重载函数

程序关联共享内存的前提下,调用该函数返回共享内存中数据的起始地址。如果没有关联共享内存,则返回0。

(10)获取共享内存的大小
int QSharedMemory::size ()const

调用该函数将返回程序所关联的共享内存的大小(字节)。如果没有关联的共享内存,则返回0。

二、线程

1.QT多线程简介

QT通过三种形式提供了对线程的支持,分别是平台无关的线程类、线程安全的事件投递、跨线程的信号-槽连接。
QT中线程类包含如下:

- QThread 提供了跨平台的多线程解决方案
- QThreadStorage 提供逐线程数据存储
- QMutex 提供相互排斥的锁,或互斥量
- QMutexLocker 是一个辅助类,自动对 QMutex 加锁与解锁
- QReadWriterLock 提供了一个可以同时读操作的锁
- QReadLocker与QWriteLocker 自动对QReadWriteLock 加锁与解锁
- QSemaphore 提供了一个整型信号量,是互斥量的泛化
- QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
2.QThread
(1)QThread线程基础

QThread是Qt线程中有一个公共的抽象类,所有的线程类都是从QThread抽象类中派生的,需要实现QThread中的虚函数run(),通过start()函数来调用run函数。
void run()函数是线程体函数,用于定义线程的功能。
void start()函数是启动函数,用于将线程入口地址设置为run函数。
void terminate()函数用于强制结束线程,不保证数据完整性和资源释放。
QCoreApplication::exec()总是在主线程(执行main()的线程)中被调用,不能从一个QThread中调用。
在GUI程序中,主线程也称为GUI线程,是唯一允许执行GUI相关操作的线程。
另外,必须在创建一个QThread前创建QApplication(or QCoreApplication)对象。
当线程启动和结束时,QThread会发送信号started()和finished(),可以使用isFinished()和isRunning()来查询线程的状态。
从Qt4.8起,可以释放运行刚刚结束的线程对象,通过连接finished()信号到QObject::deleteLater()槽。
使用wait()来阻塞调用的线程,直到其它线程执行完毕(或者直到指定的时间过去)。
静态函数currentThreadId()和currentThread()返回标识当前正在执行的线程。前者返回线程的ID,后者返回一个线程指针。
要设置线程的名称,可以在启动线程之前调用setObjectName()。
如果不调用setObjectName(),线程的名称将是线程对象的运行时类型(QThread子类的类名)。

(2)线程的优先级

QThread线程总共有8个优先级

QThread::IdlePriority   0 scheduled only when no other threads are running.			
QThread::LowestPriority  1 scheduled less often than LowPriority.			
QThread::LowPriority   2 scheduled less often than NormalPriority.			
QThread::NormalPriority  3 the default priority of the operating system.			
QThread::HighPriority   4 scheduled more often than NormalPriority.			
QThread::HighestPriority  5 scheduled more often than HighPriority.			
QThread::TimeCriticalPriority 6 scheduled as often as possible.		
QThread::InheritPriority   7 use the same priority as the creating thread. This is the default.
void setPriority(Priority priority) 

设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。
优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。

(3)线程的创建
void start ( Priority priority = InheritPriority )

启动线程执行,启动后会发出started ()信号

(4)线程的执行
int exec() [protected] 

进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则返回0。

void run() [virtual protected] 

线程的起点,在调用start()之后,新创建的线程就会调用run函数,默认实现调用exec(),大多数需要重新实现run函数,便于管理自己的线程。run函数返回时,线程的执行将结束。

(5)线程的退出
void quit();

通知线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。

void exit ( int returnCode = 0 );

调用exit后,thread将退出event loop,并从exec返回,exec的返回值就是returnCode。
通常returnCode=0表示成功,其他值表示失败。

void terminate ();

结束线程,线程是否立即终止取决于操作系统。
线程被终止时,所有等待该线程Finished的线程都将被唤醒。
terminate是否调用取决于setTerminationEnabled ( bool enabled = true )开关。

void requestInterruption() 

请求线程的中断。请求是咨询意见并且取决于线程上运行的代码,来决定是否及如何执行这样的请求。
此函数不停止线程上运行的任何事件循环,并且在任何情况下都不会终止它。

工程中线程退出的解决方案如下:
通过在线程类中增加标识变量volatile bool m_stop,通过m_stop变量的值判断run函数是否执行结束返回。

(6)线程的等待
bool wait ( unsigned long time = ULONG_MAX )

线程将会被阻塞,等待time毫秒,如果线程退出,则wait会返回。Wait函数解决多线程在执行时序上的依赖。

void msleep ( unsigned long msecs )		
void sleep ( unsigned long secs )		
void usleep ( unsigned long usecs )
sleep()msleep()usleep()允许秒,毫秒和微秒来区分,但在Qt5.0中被设为public

一般情况下,wait()和sleep()函数应该不需要,因为Qt是一个事件驱动型框架。
考虑监听finished()信号来取代wait(),使用QTimer来取代sleep()。

(7)线程的状态
bool isFinished () const  //线程是否已经退出
bool isRunning () const   //线程是否处于运行状态
(8)线程的属性
Priority priority () const
void setPriority ( Priority priority )			
uint stackSize () const		
void setStackSize ( uint stackSize )		
void setTerminationEnabled ( bool enabled = true )  //设置是否响应terminate()函数
(9)线程与事件循环

QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。
exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。
线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。

3.线程同步
(1)线程同步基础

临界资源:每次只允许一个线程进行访问的资源
线程间互斥:多个线程在同一时刻都需要访问临界资源线程锁能够保证临界资源的安全性,通常,每个临界资源需要一个线程锁进行保护。
线程死锁:线程间相互等待临界资源而造成彼此无法继续执行。

产生死锁的条件:

  • 系统中存在多个临界资源且临界资源不可抢占
  • 线程需要多个临界资源才能继续执行

死锁的避免:

  • 对使用的每个临界资源都分配一个唯一的序号
  • 对每个临界资源对应的线程锁分配相应的序号
  • 系统中的每个线程按照严格递增的次序请求临界资源

QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。例如,假如两个线程试图同时访问同一个全局变量,结果可能不如所愿。

(2)互斥量QMutex

QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么线程将休眠,直到拥有mutex的线程对此mutex解锁。QMutex常用来保护共享数据访问。QMutex类所以成员函数是线程安全的。

头文件声明:    #include <QMutex>	
互斥量声明:    QMutex m_Mutex;		
互斥量加锁:    m_Mutex.lock();		
互斥量解锁:    m_Mutex.unlock();

如果对没有加锁的互斥量进行解锁,结果是未定义的。互斥量的加锁和解锁必须在同一线程中成对出现。

QMutex ( RecursionMode mode = NonRecursive )
QMutex有两种模式:Recursive, NonRecursive
	●Recursive
		一个线程可以对mutex多次lock,直到相应次数的unlock调用后,mutex才真正被解锁。			
	●NonRecursive
		默认模式,mutex只能被lock一次。

如果使用了Mutex.lock()而没有对应的使用Mutex.unlcok()的话就会造成死锁,其他的线程将永远也得不到接触Mutex锁住的共享资源的机会。
尽管可以不使用lock()而使用tryLock(timeout)来避免因为死等而造成的死锁( tryLock(负值)==lock()),但是还是很有可能造成错误。

bool tryLock();
如果当前其他线程已对该mutex加锁,则该调用会立即返回,而不被阻塞。

bool tryLock(int timeout);
如果当前其他线程已对该mutex加锁,则该调用会等待一段时间,直到超时
(3)互斥锁QMutexLocker

在较复杂的函数和异常处理中对QMutex类mutex对象进行lock()和unlock()操作将会很复杂,进入点要lock(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QMutex的辅助类QMutexLocker来避免lock()和unlock()操作。
在函数需要的地方建立QMutexLocker对象,并把mutex指针传给QMutexLocker对象,此时mutex已经加锁,到退出函数后,QMutexLocker对象局部变量会自己销毁,此时mutex解锁。

头文件声明:    #include<QMutexLocker>
互斥锁声明:    QMutexLocker mutexLocker(&m_Mutex);			
互斥锁加锁:    从声明处开始(在构造函数中加锁)			
互斥锁解锁:    出了作用域自动解锁(在析构函数中解锁)
(4)QReadWriteLock

QReadWriterLock 与QMutex相似,但对读写操作访问进行区别对待,可以允许多个读者同时读数据,但只能有一个写,并且写读操作不同同时进行。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
QReadWriterLock默认模式是NonRecursive。

QReadWriterLock类成员函数如下:	
	●QReadWriteLock ( )				
	●QReadWriteLock ( RecursionMode recursionMode )				
	●void lockForRead ()				
	●void lockForWrite ()				
	●bool tryLockForRead ()				
	●bool tryLockForRead ( int timeout )				
	●bool tryLockForWrite ()				
	●bool tryLockForWrite ( int timeout )				
	●boid unlock ()
(5)QReadLocker和QWriteLocker

在较复杂的函数和异常处理中对QReadWriterLock类lock对象进行lockForRead()/lockForWrite()和unlock()操作将会很复杂,进入点要lockForRead()/lockForWrite(),在所有跳出点都要unlock(),很容易出现在某些跳出点未调用unlock(),所以Qt引进了QReadLocker和QWriteLocker类来简化解锁操作。在函数需要的地方建立QReadLocker或QWriteLocker对象,并把lock指针传给QReadLocker或QWriteLocker对象,此时lock已经加锁,等到退出函数后,QReadLocker或QWriteLocker对象局部变量会自己销毁,此时lock解锁。

(6)信号量QSemaphore

QSemaphore 是QMutex的一般化,是特殊的线程锁,允许多个线程同时访问临界资源,而一个QMutex只保护一个临界资源。QSemaphore 类的所有成员函数是线程安全的。

(7)等待条件QWaitCondition

QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。

QWaitCondition ()
bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX )			
bool wait ( QReadWriteLock * readWriteLock, unsigned long time = ULONG_MAX 			
		void wakeOne ()				
		void wakeAll ()
头文件声明:#include <QWaitCondition>		
等待条件声明:QWaitCondtion m_WaitCondition;			
等待条件等待:m_WaitConditon.wait(&m_muxtex, time);			
等待条件唤醒:m_WaitCondition.wakeAll();
(8)高级事件队列

QT事件系统对进程间通信很重要,每个进程可以有自己的事件循环,要在另外一个线程中调用一个槽函数(或任何invokable方法),
需要将调用槽函数放置在目标线程的事件循环中,让目标线程在槽函数开始运行之前,先完成自己的当前任务,而原来的线程继续并行运行。
要在一个事件循环中执行调用槽函数,需要一个queued信号槽连接。每当信号发出时,信号的参数将被事件系统记录。
信号接收者存活的线程将运行槽函数。另外,不使用信号,调用QMetaObject::invokeMethod()也可以达到相同的效果。
在这两种情况下,必须使用queued连接,因为direct连接绕过了事件系统,并且立即在当前线程中运行此方法。
当线程同步使用事件系统时,没有死锁风险。然而,事件系统不执行互斥。如果调用方法访问共享数据,仍然需要使用QMutex来保护。
如果只使用信号槽,并且线程间没有共享变量,那么,多线程程序可以完全没有低级原语。

4.可重入与线程安全

可重入reentrant与线程安全thread-safe被用来说明一个函数如何用于多线程程序。

5.线程与信号槽
(1)线程的依附性

线程的依附性是对象与线程的关系。默认情况下,对象依附于自身被创建的线程。
对象的依附性与槽函数执行的关系,默认情况下,槽函数在其所依附的线程中被调用执行。
修改对象的依附性的方法:QObject::moveToThread函数用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行。

(2)QObject与线程

QThread类具有发送信号和定义槽函数的能力。QThread主要信号如下:

  • void started();线程开始运行时发送信号
  • void finished();线程完成运行时发送信号
  • void terminated();线程被异常终止时发送信号

QThread继承自QObject,发射信号以指示线程执行开始与结束,并提供了许多槽函数。QObjects可以用于多线程, 发射信号以在其它线程中调用槽函数,并且向“存活”于其它线程中的对象发送事件。
QObject的可重入性:
QObject是可重入的,QObject的大多数非GUI子类如 QTimer、QTcpSocket、QUdpSocket、QHttp、QFtp、QProcess也是可重入的,
在多个线程中同时使用这些类是可能的。可重入的类被设计成在一个单线程中创建与使用,在一个线程中创建一个对象而在另一个线程中调用该对象的函数,不保证能行得通。
有三种约束需要注意:

  • 一个QObject类型的孩子必须总是被创建在它的父亲所被创建的线程中。这意味着,除了别的以外,永远不要把QThread对象(this)作为该线程中创建的一个对象的父亲(因为QThread对象自身被创建在另外一个线程中)。
  • 事件驱动的对象可能只能被用在一个单线程中。特别适用于计时器机制(timer mechanism)和网络模块。
    例如:不能在不属于这个对象的线程中启动一个定时器或连接一个socket,
    必须保证在删除QThread之前删除所有创建在这个线程中的对象。
    在run()函数的实现中,通过在栈中创建这些对象,可以轻松地做到这一点。
  • 虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类都不是可重入的,只能被用在GUI线程中。
    QCoreApplication::exec()必须也从GUI线程被调用。

在实践中,只能在主线程而非其它线程中使用GUI的类,可以很轻易地被解决:将耗时操作放在一个单独的工作线程中,当工作线程结束后在GUI线程中由屏幕显示结果。
一般来说,在QApplication前创建QObject是不行的,会导致奇怪的崩溃或退出,取决于平台。
因此,不支持QObject的静态实例。一个单线程或多线程的应用程序应该先创建QApplication,并最后销毁QObject。

(3)线程的事件循环

每个线程都有自己的事件循环。主线程通过QCoreApplication::exec()来启动自己的事件循环,但对话框的GUI应用程序,有些时候用QDialog::exec(),其它线程可以用QThread::exec()来启动事件循环。
就像 QCoreApplication,QThread提供一个exit(int)函数和quit()槽函数。
线程中的事件循环使得线程可以利用一些非GUI的、要求有事件循环存在的Qt类(例如:QTimer、QTcpSocket、和QProcess),使得连接一些线程的信号到一个特定线程的槽函数成为可能。
一个QObject实例被称为存活于它所被创建的线程中。关于这个对象的事件被分发到该线程的事件循环中。
可以用QObject::thread()方法获取一个QObject所处的线程。
QObject::moveToThread()函数改变一个对象和及其子对象的线程所属性。(如果对象有父对象的话,对象不能被移动到其它线程中)。
从另一个线程(不是QObject对象所属的线程)对该QObject对象调用delete方法是不安全的,除非能保证该对象在那个时刻不处理事件,使用QObejct::deleteLater()更好。一个DeferredDelete类型的事件将被提交(posted),而该对象的线程的 件循环最终会处理这个事件。默认情况下,拥有一个QObject的线程就是创建QObject的线程,而不是 QObject::moveToThread()被调用后的。
如果没有事件循环运行,事件将不会传递给对象。例如:在一个线程中创建了一个QTimer对象,但从没有调用exec(),那么,QTimer就永远不会发射timeout()信号,即使调用deleteLater()也不行。(这些限制也同样适用于主线程)。
利用线程安全的方法QCoreApplication::postEvent(),可以在任何时刻给任何线程中的任何对象发送事件,事件将自动被分发到该对象所被创建的线程事件循环中所有的线程都支持事件过滤器,而限制是监控对象必须和被监控对象存在于相同的线程中。
QCoreApplication::sendEvent()(不同于postEvent())只能将事件分发到和该函数调用者相同的线程中的对象。

(4)其他线程访问QObject子类

QObject及其所有子类都不是线程安全的。这包含了整个事件交付系统。
重要的是,切记事件循环可能正在向你的QObject子类发送事件,当你从另一个线程访问该对象时。
如果你正在调用一个QObject子类的函数,而该子类对象并不存活于当前线程中,并且该对象是可以接收事件的, 那么你必须用一个mutex保护对该QObject子类的内部数据的所有访问,否则,就有可能发生崩溃和非预期的行为。
同其它对象一样,QThread对象存活于该对象被创建的线程中 – 而并非是在QThread::run()被调用时所在的线程。
一般来说,在QThread子类中提供槽函数是不安全的,除非用一个mutex保护成员变量。
另一方面,可以在QThread::run()的实现中安全地发射信号,因为信号发射是线程安全的。

(5)跨线程的信号槽

线程的信号槽机制需要开启线程的事件循环机制,即调用QThread::exec()函数开启线程的事件循环。
Qt信号-槽连接函数原型如下:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, 
					const char *method, Qt::ConnectionType type = Qt::AutoConnection ) 

Qt支持5种连接方式:

  • Qt::DirectConnection(直连方式)(信号与槽函数关系类似于函数调用,同步执行)
    • 当信号发出后,相应的槽函数将立即被调用。emit语句后的代码将在所有槽函数执行完毕后被执行。
    • 当信号发射时,槽函数将直接被调用。
    • 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
  • Qt::QueuedConnection(队列方式)(此时信号被塞到事件队列里,信号与槽函数关系类似于消息通信,异步执行)
    • 当信号发出后,排队到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,调用相应的槽函数。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕。
    • 当控制权回到接收者所依附线程的事件循环时,槽函数被调用。
    • 槽函数在接收者所依附线程执行。
  • Qt::AutoConnection(自动方式)
    • Qt的默认连接方式,如果信号的发出和接收信号的对象同属一个线程,那个工作方式与直连方式相同;否则工作方式与队列方式相同。
    • 如果信号在接收者所依附的线程内发射,则等同于直接连接
    • 如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接
  • Qt::BlockingQueuedConnection(信号和槽必须在不同的线程中,否则就产生死锁)
    • 槽函数的调用情形和Queued Connection相同,不同的是当前的线程会阻塞住,直到槽函数返回。
  • Qt::UniqueConnection
    • 与默认工作方式相同,只是不能重复连接相同的信号和槽,因为如果重复连接就会导致一个信号发出,对应槽函数就会执行多次。

QThread是用来管理线程的,QThread对象所依附的线程和所管理的线程并不是同一个概念。QThread所依附的线程,就是创建QThread对象的线程,QThread 所管理的线程,就是run启动的线程,也就是新建线程。
QThread对象依附在主线程中,QThread对象的slot函数会在主线程中执行,而不是次线程。除非QThread对象依附到次线程中(通过movetoThread)。

6.其他介绍略。
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值