进程与线程(理论篇)
进程与线程(理论篇)
多道程序设计
多道程序设计(multi-programming)是操作系统所采用的最基本、最重要的技术,其根本目的是提高整个计算机系统的效率。
1 |
|
这里的吞吐可以用来衡量系统效率。
单道程序设计的问题
所谓单道程序设计,就是一次只允许一个程序进入系统的程序设计方法。显而易见,这样就有一个严重的缺点:资源利用率低。
- 设备资源利用率低:计算机系统有许多外围设备,在单道程序中,一次只能使用外围设备中的一个子集,而未被使用的设备会被浪费。
- 内存资源利用率低:随着硬件技术的提升,内存容量不断扩大,如果单道程序使用这么大的内存,其内存浪费空间是惊人的。
- 处理器资源利用率低:单道程序设计的方式导致CPU在I/O操作和其他阻塞期间的空闲,影响了整体资源利用率。
单道程序设计还有个缺点是不能表达程序内在并行性。比如Windows中使用Word文档时,在打字时需要有下面操作:①在缓冲区交互式地编辑用户输入;②对缓冲区内用户输入内容进行语法检查;③将缓冲区的内容定时保存到磁盘上。显然如果操作一频繁,这三道程序是无法安排执行顺序的,它们其内在就是并行的。
多道程序设计的提出
多道程序设计的提出源于对计算资源利用率的提升需求,尤其是在20世纪60年代初期,计算机处理速度迅速提高,而I/O设备的速度相对较慢。为了解决单道程序设计中的低资源利用率和高等待时间问题,研究人员和工程师们开发了多道程序设计。
这种设计允许多个程序同时驻留在内存中,使得CPU可以在一个程序等待I/O操作时,切换到另一个程序继续执行,从而充分利用处理器时间。通过这种方式,多道程序设计实现了更高的并行性和资源利用率,减少了空闲时间,并提高了系统的整体效率。最终,这种设计成为现代操作系统的基础,推动了计算机技术的发展。
相应与单道程序设计,它对以下方面都有所提升:
- 设备资源利用率提高
- 内存资源利用率提高
- 处理器资源利用率提高
多道程序设计的问题
多道程序设计改善了系统资源的使用情况,从而增加了吞吐量,提高系统效率。但是也带来了新的问题:资源竞争。
内存管理复杂性:随着多个程序同时驻留在内存中,内存分配和管理变得复杂。需要有效地管理内存,以防止内存泄漏、碎片化等问题。
上下文切换开销:频繁地在多个程序之间切换会消耗CPU时间和系统资源,尤其是在上下文切换(保存和恢复程序状态)频繁的情况下,这可能导致性能下降。
资源竞争:多个程序同时请求同一资源(如I/O设备、CPU时间等),可能导致竞争和冲突,进而影响系统的稳定性和性能。
调度算法复杂性:为了公平和高效地分配CPU时间,需要设计复杂的调度算法。这些算法需要考虑优先级、响应时间、周转时间等多种因素,增加了系统设计的复杂性。
错误和故障传播:一个程序的错误可能会影响到整个系统的稳定性,特别是在多道程序设计中,程序之间的相互影响和依赖关系可能导致更严重的问题。
调试和测试难度:在多道程序环境中,调试和测试变得更加困难,因为多个程序的交互可能导致难以重现的错误。
进程的引入
由上述我们知道,在多道程序系统中运行的程序是一个时断时续的一个状态,其运作规律:一个程序获得处理器资源后向前推进,当它未得到某种资源时就暂停下来。
1 |
|
当程序暂停时,需要将其现场的信息作为断点保护起来,以便以后再次推进时能够恢复上次暂停时的现场信息并从断点处开始继续执行。这样,在多道程序设计系统中运行的程序只需要一个保存断点现场信息的区域,而这个区域并不是程序的组成部分,因此就需要一个能够更准确地描述多道程序系统中执行程序的术语,这就是进程(process)。
状态及状态转换
进程的状态:
- 运行态:进程占有处理器资源,正在运行。
- 就绪态:进程本身具备运行条件,但是由于处理器的数量少于可运行进程的数量,暂未投入运行
- 阻塞态:也称等待态、挂起态、睡眠态。进程本身不具备运行条件,即使分给其处理器也不能运行。进程正在等待一个事情发生,如等待某一资源被释放,等待与该进程相关的数据传输完成信号等。
状态转换关系如下图:
控制块
进程控制块(Process Control Block, PCB)是操作系统用于管理进程的重要数据结构。它包含了一个进程在执行期间所需的所有信息,主要包括以下内容:
- 进程标识符:每个进程的唯一ID,帮助系统区分不同进程。
- 进程状态:指示进程当前的状态,如运行、就绪、阻塞等。
- 程序计数器:记录进程下一条将要执行的指令的地址。
- CPU寄存器:保存进程执行时的寄存器状态,以便在上下文切换时恢复。
- 内存管理信息:包括进程的地址空间、页表等信息,帮助操作系统管理内存。
- 调度信息:与进程调度相关的优先级、调度队列指针等信息。
- I/O状态信息:记录进程所需的I/O设备及其状态,帮助管理I/O操作
组成与上下文
进程由两个部分组成,即进程控制块和程序,其中程序包括代码和数据等。
- 程序代码:进程所执行的指令,通常存储在内存中。
- 进程数据:用于存储程序运行时的数据,包括全局变量、局部变量和堆数据等。
- 进程控制块(PCB):前面提到的用于管理进程的结构,包含进程的状态、标识符、寄存器信息等。
- 堆栈:用于存放函数调用时的局部变量和返回地址,支持程序的递归和函数调用。
关于上下文,上下文是指进程在执行时的状态信息,主要包括:
- CPU寄存器:当前寄存器的值,保存进程执行时的状态。
- 程序计数器:指向进程下一条要执行的指令。
- 内存映射:当前进程使用的内存空间信息。
在上下文切换时,操作系统会保存当前进程的上下文(如寄存器和程序计数器),然后加载下一个进程的上下文,从而实现多进程的切换与执行。这使得多个进程能够在同一CPU上并发运行。
队列
进程的队列是操作系统中用于管理和调度进程的重要数据结构,通常由多种不同类型的队列组成。以下是常见的进程队列及其功能:
- 就绪队列:
- 包含所有准备好运行但尚未被CPU执行的进程。
- 操作系统调度程序从此队列中选择进程进行调度。
- 阻塞队列(或等待队列):
- 包含正在等待某些事件(如I/O操作完成、信号等)的进程。
- 当事件发生时,相关进程会从阻塞队列中移除并进入就绪队列。
- 运行队列:
- 这个概念通常指的是当前正在使用CPU的进程。
- 在多处理器系统中,可能存在多个运行队列,每个CPU都有自己的运行队列。
- 新建队列:
- 包含刚创建但尚未进入就绪状态的进程。
- 这些进程正在初始化和分配资源。
- 终止队列:
- 包含已完成执行的进程,等待操作系统回收资源。
- 这些进程将被移除,相关的PCB也会被释放。
- 优先级队列:
- 在某些调度算法中,进程根据优先级被放入不同的队列。
- 高优先级进程会比低优先级进程更早被调度。
创建
- 创建过程
- 系统调用:进程创建通常通过系统调用实现。在Unix/Linux系统中,常用的系统调用是
fork()
。在Windows系统中,常用的调用是CreateProcess()
。 - 复制资源:在使用
fork()
时,操作系统会创建一个新的进程控制块(PCB)并复制父进程的资源,包括内存空间、打开的文件描述符、信号处理等。 - 返回值:
fork()
返回值有两种情况:- 在父进程中返回子进程的进程ID。
- 在子进程中返回0。
- 初始化新进程
- 独立的资源:虽然子进程复制了父进程的资源,但它们是独立的。对子进程的修改不会影响父进程,反之亦然。
- 执行新程序:子进程可以使用
exec()
系列系统调用来加载并执行一个新的程序,这个过程将替换当前进程的映像。
- 进程树
- 进程间关系:进程以树形结构组织,父进程可以创建多个子进程,每个子进程也可以创建自己的子进程,形成进程树。
- 进程ID:每个进程都有一个唯一的进程ID(PID),它用于标识和管理进程。
- 进程的终止
- 子进程的终止:子进程可以通过调用
exit()
系统调用结束自己,操作系统会清理其资源,并将其状态信息保留在父进程中,以便父进程可以读取(通过wait()
系统调用)。 - 孤儿和僵尸进程:
- 孤儿进程:如果父进程先于子进程终止,子进程会成为孤儿进程,操作系统会将其父进程改为
init
进程(PID为1)。 - 僵尸进程:如果子进程结束,但父进程未调用
wait()
来收集其状态信息,子进程会变成僵尸进程,保留在系统中以等待父进程处理。
- 孤儿进程:如果父进程先于子进程终止,子进程会成为孤儿进程,操作系统会将其父进程改为
进程与程序的区别
程序
- 定义:程序是一个静态的代码集合,它是由计算机语言编写的指令的集合,通常以文件的形式存储在磁盘上。程序本身并不执行,它只是一组指令和数据的描述。
- 作用:程序的作用是定义计算机执行特定任务的步骤和逻辑。程序可以是任何形式的应用,例如文本编辑器、浏览器、游戏等。
进程
- 定义:进程是程序在执行时的一个动态实例,它是程序运行时的状态,包括程序代码、程序计数器、寄存器内容、变量、堆栈、内存分配等。每个进程都有其自己的地址空间和系统资源。
- 作用:进程是操作系统管理和调度的基本单位。它代表了程序在计算机上运行时的状态,使得多个程序可以同时运行而不相互干扰。
主要区别
- 状态:
- 程序是静态的,而进程是动态的。
- 存在形式:
- 程序存在于存储介质上(如硬盘),而进程在内存中运行。
- 资源管理:
- 程序本身不需要资源,进程则需要CPU、内存等资源来执行。
- 调度与执行:
- 程序在需要时被加载成进程,而进程可以被调度执行、挂起或终止。
线程与轻进程
早期的操作系统是基于进程的,一个进程中只包含一个执行流,进程是处理器调度的基本单位。当处理器由一个进程切换到另一个进程时,整个上下文都要发生变化,系统开销比较大,相关进程耦合度差。
在许多应用中,一些执行流之间具有内在逻辑关系,设计相同的代码或数据。如果将这些执行流放在同一进程的框架下,则这些执行流之间的切换便不涉及地址空间的变化,这就是线程思想的由来。
线程
线程是计算机程序中的一个基本执行单元,它是进程中的一个独立运行的子任务。线程在同一进程内共享内存空间和资源,但拥有自己的执行上下文。以下是线程的一些关键概念:
- 轻量级进程:线程常被称为轻量级进程,因为它们比进程更轻便,创建和销毁的开销较小。
- 并发性:线程允许程序同时执行多个任务,这样可以提高程序的响应性和资源利用率。例如,用户界面可以在一个线程中运行,而后台任务(如数据处理)可以在另一个线程中进行。
- 共享资源:同一进程中的线程可以共享全局变量和堆内存,便于数据交换,但也需要小心处理以避免数据竞争和不一致性。
- 线程状态:线程的生命周期包括多个状态,如新建、就绪、运行、阻塞和终止,操作系统根据这些状态调度线程的执行。
- 同步和互斥:由于多个线程可能会同时访问共享资源,因此需要使用同步机制(如锁、信号量等)来防止数据不一致和竞争条件。
- 调度:操作系统通过线程调度算法决定哪个线程获得 CPU 的使用权,以实现高效的多任务处理。
- 优先级:线程可以设置优先级,以影响它们获得 CPU 时间的顺序。
线程的结构
线程的结构主要包含以下几个核心部分:
- 线程标识符(Thread ID):
- 每个线程都有一个唯一的标识符,用于区分不同的线程。
- 程序计数器(Program Counter, PC):
- 记录线程当前执行的指令地址。每个线程都有自己的程序计数器,以独立追踪执行位置。
- 堆栈(Stack):
- 每个线程拥有自己的堆栈,用于存储局部变量、方法调用的参数、返回地址等信息。堆栈在函数调用和返回时动态变化。
- 线程状态(Thread State):
- 线程可以处于不同状态,如新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Terminated)。这些状态反映了线程的生命周期。
- 优先级(Priority):
- 每个线程可以设置优先级,影响其被调度执行的顺序。操作系统使用优先级来管理线程竞争 CPU 时间。
- 共享资源:
- 同一进程内的线程共享全局变量和堆内存,这使得线程之间可以高效通信,但也需要注意同步问题。
- 同步机制:
- 为了避免数据竞争和确保数据一致性,线程间需要使用同步机制,如互斥锁、读写锁和信号量等。
- 线程本地存储(Thread Local Storage, TLS):
- 一些数据可以为每个线程单独存储,避免线程间的干扰和数据共享。
注意图中的“逻辑寄存器”,这是想强调在单CPU系统中,硬件寄存器只有一套,可以理解为每个进程有一组虚拟的寄存器。
控制块
线程控制块(Thread Control Block, TCB)是操作系统用于管理线程的重要数据结构。它包含了关于线程的关键信息,具体包括:
- 线程标识符(Thread ID):唯一标识该线程。
- 线程状态:记录线程的当前状态(如就绪、运行、阻塞等)。
- 程序计数器(Program Counter, PC):指向当前执行的指令地址。
- 堆栈指针:指向该线程的堆栈顶部,用于管理线程的局部变量和函数调用。
- 优先级:线程的优先级信息,用于调度。
- 上下文信息:包括寄存器内容、调度信息等,确保线程切换时能够正确恢复状态。
- 共享资源信息:描述该线程访问的共享资源,以管理同步和互斥。
- 调度信息:与调度算法相关的数据,用于管理线程的执行顺序。
实现
线程有两种实现方式:在目态实现的用户级别线程,在管态实现的核心级别线程。
- 用户级线程(User-Level Threads, ULT)
- 管理方式:线程的创建和管理在用户空间进行,操作系统不直接感知这些线程。
- 优点:上下文切换开销较小,因为切换只涉及用户态的操作;线程的创建和销毁速度快。
- 缺点:操作系统无法调度用户级线程,如果一个线程进入阻塞状态,整个进程都会被阻塞;无法充分利用多核处理器,因为操作系统只看到进程,不了解内部线程。
- 内核级线程(Kernel-Level Threads, KLT)
- 管理方式:线程的创建、管理和调度由操作系统内核完成,每个线程都有自己的内核控制块。
- 优点:操作系统能够识别和调度每个线程,可以在多个核心上并行执行,提高性能;如果一个线程阻塞,其他线程仍可继续运行。
- 缺点:上下文切换开销较大,因为涉及内核态和用户态之间的切换;创建和销毁线程的成本也较高。
- 混合线程(Hybrid Threads)
- 管理方式:结合用户级线程和内核级线程的优势,通常采用多线程模型,如轻量级进程(LWP)。
- 优点:用户级线程的管理提供了灵活性和快速切换,而内核级线程的调度能力使得可以有效利用多核处理器。
- 实现:用户级线程在用户空间管理,而内核可以调度多个用户级线程至内核级线程,达到更好的性能和资源利用。
应用
- 并行处理
在计算密集型任务中,线程可以同时执行多个计算任务,提高整体处理速度。例如,在科学计算、图像处理和数据分析等领域,线程可以分担复杂的计算任务。
- 响应性提升
在用户界面(UI)应用中,使用线程可以保持界面的响应性。例如,长时间运行的任务可以在后台线程中执行,使得主线程可以继续处理用户输入。
- 服务器应用
在网络服务器(如Web服务器、数据库服务器)中,线程通常用于处理多个客户端请求。每个请求可以分配一个线程,使得服务器能够并发处理多个连接,提高吞吐量和响应速度。
- 实时系统
在实时应用中,线程可以根据优先级调度,确保高优先级任务及时响应。这在嵌入式系统、工业控制和金融交易系统中尤为重要。
- 数据处理
在大数据处理和流处理应用中,线程可以用于并行读取、处理和存储数据。例如,MapReduce框架利用多线程来并行处理大量数据。
- 游戏开发
在游戏开发中,线程用于管理多个并发任务,如渲染、物理计算和输入处理,提升游戏的性能和流畅度。
- 多媒体应用
在视频和音频处理应用中,线程可以并行处理音视频流,确保播放和处理的实时性。