线程概述
早期操作系统中并没有线程这一概念,无论是分配资源还是调度分派,都以进程为最小单位,随着计算机技术的发展,人们逐渐发现了进程作为系统调度分派单位时存在的一些弊端:由于进程是资源的拥有者,使用独立的地址空间,因此当系统切换资源时,内存中进程的数据段、代码段以及堆栈都要被切换,这种情况下无论是时间消耗还是空间消耗都相当大;此外,操作系统允许多个进程并行执行,但由于进程较为庞大,多个进程占用的空间是相当可观的。基于以上两点,人们意识到操作系统应能调度一个更小的单位,以减少消耗,提高效率,由此,线程应运而生。
Linux系统中的线程借助进程机制实现,线程与进程联系密切:进程可以蜕变成线程,当在一个进程中创建一个线程时,原有的进程就会变成线程,两个线程共用一段地址空间;线程又被称为轻量级进程,线程的TCB(Thread Control Block,线程控制块)与进程的PCB相同,因此也可以将TCB视为PCB;对内核而言,线程与进程没有区别,cpu会为每个线程与进程分配时间片,并通过PCB来调度不同的线程和进程。轻量级线程与内核及cpu之间的关系如图1所示。
图1 LWP与内核及cpu关系示意图
一个进程的实体可以分为两大部分:线程集和资源集。线程集是多个线程的集合,每个线程都是进程中的动态对象;资源集是进程中线程集共享资源的集合,包括地址空间、打开的文件描述符、用户信息等。一个线程的实体包括程序、数据、TCB以及少量必不可少的、用于保证线程独立运行的资源,当然,线程中也包含一部分私有数据,如程序计数器、栈空间及寄存器等。
使用多线程编程时,程序的并发性会得到一定提升。如图9-1,若系统中的进程是单线程进程,那么进程中的命令只能在一个处理器上顺序执行;而若一个进程细分为多个线程,一个进程中的多个线程可以同时在不同的cpu上运行,如此在一定程度上减少了程序的执行时间,提高了程序的执行效率。
虽然线程与进程联系密切,有时线程还可以被称为轻量级进程(Lightweight Process,LWP),但它们仍是有区别的,其中最大的区别在于:线程PCB中指向内存资源的三级页表相同,而进程PCB中指向内存资源的三级页表不同。
由于多个进程PCB中的三级页表指向不同的内存资源,因此即便不同的进程使用相同的虚拟地址也不会发生冲突;但一个进程地址空间里多个线程的PCB中指向三级页表的指针是相同的:线程PCB中的页目录指针指向相同的页目录、页目录对应相同的页表,最终页表又对应磁盘上相同的物理页面,也就是说,多个线程的虚拟地址会被映射到物理磁盘的同一段地址。如图2所示,为线程间的地址映射关系。
图2 线程间的地址映射
运行在同一个进程地址空间的多个线程共享虚拟地址空间,进而共享相同的页目录、页表和物理页面,因此线程间的许多数据是共享的,线程不必通过类似进程通信使用的管道、信号量等机制,便能进行通信。但线程的缺点也随之而来:因为多个线程共享一段地址空间,当多个线程同时需要对其中的数据进行访问时,可能会因竞争导致读写错误,因此,正如控制多个进程对共享资源的访问一样,系统同样也应实现对线程间的共享数据的同步。
与进程相比,线程具有开销小、数据通信与共享数据方便等优点,并能在一定程度上提高程序并发性,但它也有不足:因为线程使用的是库函数,所以不够稳定;另外由于线程出现时间较晚,gdb中没有添加调试线程的方法,因此线程的调试和编写比较困难,基于同样的原因,线程对信号的支持也比较差。当然,线程的缺点不足以影响线程的使用,下面我们将从线程的操作入手,来学习在编程中使用线程的方法。