多线程可以让应用程序拥有更加出色的性能,与此同时如果没有用好,多线程又是比较容易出错且难以查找到错误所在,甚至可以让人们觉得自己陷进了泥潭。作为一名C/C++/QT程序员,掌握好多线程开发技术是学习的重中之重。
1,让软件拥有灵敏的响应
在单线程软件中,同时存在多个任务时,比如读写文件、更新用户界面、网络连接、打印文档等,按照先后次序执行,即先完成前面的任务再执行后面的任务,如果某个任务执行的时间较长,比如读写一个大文件,那么用户界面无法及时更新,软件没有任何响应,看起来像死机一样,用户体验很不好。怎么解决这个问题呢?人们提出了多线程编程技术。在采用多线程编程技术的程序中,多个任务由不同的线程去执行,不同线程各自占用一段CPU时间,即使线程任务还没完成,也会让出CPU时间给其他线程去执行。这样在用户角度看起来好像几个任务时同时进行的,至少界面上能得到及时更新,大大改善了用户对软件的体验,提高了软件的响应速度和友好度。
2,充分利用多核处理器
随着多核处理器日益普及,单线程的程序愈发成为性能的瓶颈。比如计算机有2个CPU核,单线程软件同一时刻只能让一个线程在一个CPU核上运行,另外一个核就可能空闲在那里,无法发挥性能。如果软件设计了2个线程,则同一时刻可以让两个线程在不同的CPU核上同时运行,运行效率增加了一倍。
3,更高效的通信
对于同一进程的线程来说,它们共享该进程的地址空间,可以访问相同的数据。通过数据共享方式使得线程之间的通信比进程之间的通信更高效和方便。
4,开销比进程小
创建线程、线程切换等这些操作所带来的系统开销比进程的类似操作所需开销要小得多。由于线程共享进程资源,因此创建线程时不需要再为其分配内存空间等资源,创建时间也更短。比如在Solaris2操作系统上,创建进程得时间大约是创建线程得30倍。线程作为基于执行单元,当从同一个进程的某个线程切换到另一个线程时,需要载入的信息比进程之间要少,所以切换速度快,比如Solaris2操作系统中线程的切换比进程快大约5倍。
现代操作系统大多支持多线程,每个进程中至少有一个线程,即使没有使用多线程编程技术,进程也含有一个主线程,所以也可以说CPU中执行的是线程。线程是程序的最小执行单位,是操作系统分配CPU时间的最小实体。一个进程的执行说到底是从主线程开始的,如果需要可以在程序任何地方开辟新的线程,其他线程都由主线程创建。一个进程正在运行,也可以说是一个进程中的某个线程正在运行。一个进程的所有线程共享该进程的公共资源,比如虚拟地址空间、全局变量等。每个线程也可以拥有自己私有的资源,如堆栈、在堆栈中定义的静态变量和动态变量、CPU寄存器的状态等。
线程总是在某个进程环境中创建的,并且会在这个进程内部销毁,正所谓“始于进程而终于进程”。线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一个内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其他线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和线程栈,线程栈用于维护线程在执行代码时所需的所有函数参数和局部变量)。
相对于进程来说,线程所占用资源更少,比如创建进程,系统要为进程分配很大的私有空间,占用的资源较多;对多线程程序来说,由于多个线程共享一个进程地址空间,因此占用资源较小。此外,在进程之间切换时需要交换整个地址空间;而在线程之间切换时只是切换线程的上下文环境,因此效率更高。在操作系统中引入线程带来的主要好处是:
(1)在进程内创建,终止线程比创建、终止进程要快。
(2)同一个进程内的线程间切换比进程间的切换要快,尤其是用户级线程间的切换。
(3)每个进程都具有独立的地址空间,而该进程内的所有线程共享该地址空间,因此线程的出现可以解决父子进程模型中子进程必须复制父进程空间的问题。
(4)线程对解决客户/服务器模型非常有效。
虽然多线程给应用开发带来了不少好处,但并不是所有情况下都要去使用多线程,要具体问题具体分析,通常在下列情况下可以考虑使用多线程:
(1)应用程序中的各任务相对独立。
(2)某些任务耗时较多。
(3)各任务有不同的优先级。
(4)一些实时系统应用。
- 线程的状态
一个线程从创建到结束是一个声明周期,总是处于下面4个状态中的一个。
(1)就绪态
就绪态表示线程能够运行的条件已经满足,只是在等待处理器(处理器要根据调度策略把就绪态的线程调度到处理器中运行)。处于就绪态的原因可能是线程刚刚被创建(刚创建的线程不一定马上运行,一般先处于就绪态),或可能刚刚从阻塞状态中恢复,或可能被其他线程抢占而处于就绪态。
(2)运行态
运行态表示线程正在处理器中运行,正占用着处理器。
(3)阻塞态
由于在等待处理器之外的其他条件而无法运行的状态叫作阻塞态。这里的其他条件包括I/O操作、互斥锁的释放、条件变量的改变等。
(4)终止态
终止态就是线程的线程函数运行结束或被其他线程取消后所处的状态。处于终止态的线程虽然已经结束,但是其所占用的资源还没有被回收,而且可以被重新复活。我们不应该长时间让线程处于这种状态。线程处于终止态后应该及时进行资源回收。
- 线程函数
线程函数就是线程创建后进入运行态要执行的函数。执行线程,说到底就是执行线程函数。这个函数是我们自定义的,然后在创建线程时把该函数名作为参数传入线程创建函数。
同理,中断线程的执行,就是中断线程函数的执行,以后恢复线程的时候就会从前面线程函数暂停的地方开始继续执行下面的代码。结束线程也就不再运行线程函数了。
线程函数可以是一个全局函数或类的静态函数,比如在POSIX线程库中,它通常是这样声明:
void *ThreadProc(void *arg)
其中,参数arg指向要传给线程的数据,这个参数在创建线程的时候作为参数传入线程创建函数中的。函数的返回值应该表示线程函数运行的结果:成功还是失败。注意,函数名ThreadProc是可以自定义的,是用户自己先定义好再由系统来调用的函数。
在QT中,线程函数是一个不能直接调用、需要实现的虚拟函数:
QThread::run()
我们通常需要自己继承QThread,并实现run()函数,然后由系统来调用。
- 线程标识
既然句柄是用来标识线程对象的,那么线程本身用什么来标识呢?在创建线程的时候,系统会为线程分配一个唯一的ID作为线程的标识,这个ID号从线程创建开始就存在,一直伴随着线程的结束才消失。线程结束后该ID就不存在了,不需要去显式清除。
通常线程创建成功后会返回一个线程ID。
网友评论