对于延时的操作,开启多个线程,并各自负责对应的内容(后台逻辑、GUI等),是正常不过的了。

开启子线程的流程:
——创建thread类并继承QThread。重写run函数(这个函数,就是子线程实际执行的内容了)。
——实例化thread类对象,并调用start函数,这就意味着子线程的开启。run函数已经在运行。
——QThread提供了一个terminate函数,可以在线程还在运行时终止它的运行。不过使用terminate函数是不被接受的,因为它会在任何地点停止这个线程并且不会给这个线程任何机会来清空它自己。更加建议的是,设置一个 volatile 变量,可以在其他线程里改变它,,然后把它作为run函数执行下去的前提,比如作为 while 的判断条件。这样子就可以在合适的时机停止 run 之类的了。

线程的同步和通信的方式:
其实,我还不是很确定同步和通信到底是不是可以划等号。但是,从某种意义上讲,同步应该还是通信的一种,但是会要求更多。
Qt提供的几种用于同步 / 通信的类,有QMutex、 QMutexLocker、QSemaphore 、QWaitCondition。
原理上,以上的方式都是授权给具体的某一个线程,针对共享的空间,即数据段(多线程的动态栈,静态堆之类都是共享的),进行一个时间段的所有权控制,在这个时间段里,可以读写这个空间,其他线程是不能读写的。等到完成读写后,退出所有权,其他线程才有可能控制这个空间。
所有权的控制是基础,如果对于线程同步来说,最好还需要在达到某个条件后,阻塞这个线程,让下一个线程去执行到应该达到的地步,实现预期的同步。
对于通信来说,其实只要共享就可以了。所以,只要都能抓取这个空间就好了,是这样吧。

以下是对具体实现方式的介绍:

1-Mutex,互斥锁。
就是实例化QMutex对象,对于一个共享的数据段,线程可以去用 QMutex 的 lock函数,锁着它。其他的线程就无法访问,线程完成操作后,用 unlock函数解锁它,其他有需要访问这个数据段的线程就可能去锁住它,但在之前的这个等待过程中,会被阻塞。(特别注意的是,Mutex如果是要用于多线程的通信同步时,一定要起码两个线程对象在一个Mutex上才行,这就意味着 Mutex 要用静态的,才能完成。)

void Thread::run(){
	//mutex 是在Thread头文件里static 的 QMutex  对象
	mutex.lock();     	//锁住,其他线程无法访问
	num++;    			//在锁住这段时间里,修改共享的数据内容
	mutex.unlock(); 	//解锁,退出控制权
}

2-QMutexLocker+QMutex:
在复杂额函数里锁住和解锁互斥量,特别是使用 c++ 异常函数,可能会有错误发生,于是通过 QMutexLocker+QMutex 来简化互斥量处理。先实例化 QMutexLocker 这样一个对象,其构造函数,就是接收一个 QMutex 对象指针并锁住它,当QMutexLocker 实现析构函数时,就自动解锁这个 QMutex 对象(所以,要清醒地知道,这个 QMutexLocker 到底析构了没有)。

void Thread::run(){
	//mutex 是在Thread头文件里static 的 QMutex  对象
	QMutexLocker locker(&mutex);     	//实例化 QMutexLocker对象了,也就 锁住,其他线程无法访问
	num++;    			//在锁住这段时间里,修改共享的数据内容
	// locker 是局域变量,作用区间只有这个run函数里,
	// 所以到达大括号末端后,locker 使用析构函数,解锁,退出控制权
}

3-QSemaphore 信号量:
信号量可以说,就是互斥量的泛化。先是通过实例化 QSemaphore 对象,其构造函数的参数,就是要保护的资源数量。然后通过 QSemaphore 对象的后缀修饰符 ++ 、–来获得由这个信号量保护的单个资源(这单个资源就要看你的的那个大小是多少了,如果说是int [],就是4个字节,char []就是1个字节)。
使用信号量的优点在于,我们同样保证了线程对共享数据的所有权控制,又极大地拓展了空间。我们可以传递大于1的数字给构造函数,之后可以多次调用 ++,来获取多个资源。++、–就是在单个单个地得到 / 释放第1 、第2……单元的控制权,但是你在读取 / 写入的时候,它并没有自动地移位到第1、第2……单元的位置,需要自己在程序里移位才行。
下面的代码,左边是等于右边的。

QSemaphore  semaphore(1);				QMutex mutex;  
semaphore ++;							mutex.lock();
semaphore --;							mutex.unlock();
#define BufferSize 1000
#define DataSize 4096
// buffer 就是共享的那段数据段
char buffer[BufferSize ];
QSemaphore  FreeSpace(BufferSize);
QSemaphore  UseSpace(BufferSize);

void ThreadProducer::run(){
	for(int i = 0; i < DataSize; i++){
		// 就是控制权的移位
		semaphore++;
		//写入资源,这就是自己来移位
		buffer[ i %  BufferSize ] = "ABCD"[(unsigned int)rand() % 4];
	}
	for(int i = 0; i < DataSize; i++){
		// 就是控制权的移位
		semaphore--;
	}
}

4- QWaitCondition 和 QMutex:
QWaitCondition 允许一个线程,在一定条件下去唤醒其他线程。使用的是,wait函数和wake函数、或者wakeAll(可能还有其他的,看手册,才是最完全的)。
wait,就是阻塞当前线程,一直在等待条件满足。
wake,就是去唤醒 / 提示其他线程,线程要unlock了,可以准备抓取互斥锁了。
同时,wait还可以接收一个QMutex的对象指针,作用就是,如果不符合条件,我就先解锁(这个锁是先锁上的了),让其他线程去操作,自己阻塞了这个线程,等待。等到条件满足之后,就锁上这个锁。
左边等于右边

if(usedSpace == BufferSize){				while(usedSpace == BufferSize){
	mutex.unlock();								bufferIsNotNull.wait(&mutex);
	bufferIsNotNull.wait();					}
	mutex.lock();
}

总结:
——前面的1-3种方式,都是需要依赖于线程 / 设备自己的响应程度(即是,发现了解锁了去抓取的速度)。但是第4种,是自己去提示其他在等待(正在阻塞)的线程,所以是最保险的线程同步。(线程同步还可以有,信号与槽)
——如果是线程通信的话,1-4都可以(信号与槽也行,但是不是最佳)。
——注意,互斥锁是使用全局变量。
——QMutexLocker 需要考虑解析函数什么时候完成。