进程与线程
- 概念
- 进程:
对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;- 线程:
进程的子任务,是CPU调度和分派的基本单位,实现进程内部的并发;
线程是操作系统可识别的最小执行和调度单位。
每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
- 区别
- 拥有资源
进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。- 调度
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。- 系统开销
由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。- 通信方面
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
- 进程间通信IPC
- 管道
- 消息队列
- 信号量
- 信号
- 共享内存
- 套接字SOCKET
- 线程间的同步方式(线程锁)
- 信号量
它只取自然数值,并且只支持两种操作:
P(SV): 如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。
V(SV): 如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。- 互斥量
互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。- 条件变量
条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。
- 线程间通信
- 使用全局变量
主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile- 使用消息实现通信
在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。- 使用事件CEvent类实现线程间通信
Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
- 并发与并行
- 并发:
指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。- 并行:
指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。
- 进程状态转换图
- 5个基本状态
创建状态:进程正在被创建。
就绪状态:进程被加入到就绪队列中等待CPU调度运行。
执行状态:进程正在被运行。
等待阻塞状态:进程因为某种原因,比如等待I/O,等待设备,而暂时不能运行。
终止状态:进程运行完毕。- 交换技术
当多个进程竞争内存资源时,会造成内存资源紧张,并且,如果此时没有就绪进程,处理机会空闲,I/0速度比处理机速度慢得多,可能出现全部进程阻塞等待I/O。
- 交换技术:换出一部分进程到外存,腾出内存空间。
在交换技术上,将内存暂时不能运行的进程,或者暂时不用的数据和程序,换出到外存,来腾出足够的内存空间,把已经具备运行条件的进程,或进程所需的数据和程序换入到内存。从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。
- 虚拟存储技术:每个进程只能装入一部分程序和数据。
- 进程调度
- 批处理系统
保证吞吐量和周转时间(从提交到终止的时间)
- 先来先服务 first-come first-serverd(FCFS)
非抢占式的调度算法,按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。- 短作业优先 shortest job first(SJF)
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。- 最短剩余时间优先 shortest remaining time next(SRTN)
最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
- 交互式系统
快速地进行响应
- 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。- 优先级调度
为每个进程分配一个优先级,按优先级进行调度。
为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。- 多级反馈队列
多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
- 进程的内存空间
进程的内存模型
- 堆和栈的区别
- 大小限制:
栈底的地址和栈的最大容量是系统预先规定好的(2M/1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间, stack overflow。因此,能从栈获得的空间较小。堆是用链表来存储的不连续内存区域,大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。- 申请效率:
栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.- 存储内容:
栈存储返回地址,参数,局部变量。堆在这块内存空间中的首地址处记录本次分配的大小,具体内容由程序员安排。- 数据访问:
存储在堆中的对象是全局可以被访问的,然而栈内存不能被其他线程所访问,且遵循LIFO原则。
- 生命周期:
栈内存的生命周期很短,而堆内存的生命周期从程序的运行开始到运行结束。
- 进程关系
进程通常不是凭空独立的出现的,在类Unix系统中,所有的其他进程都是从 进程0 fork 出来的,每个进程都会拥有多个子进程。
- fork()
- fork给父进程返回子进程pid,给其拷贝出来的子进程返回0,
- 这也是他的特点之一,一次调用,两次返回。
- 实质是在子进程的栈中构造好数据后,子进程从栈中获取到的返回值。
由于现代操作系统的写时复制机制,即使我们知道每个进程都拥有自己独立的地址空间,其实子进程指向的物理内存是和父进程相同的(代码段,数据段,堆栈都指向父亲的物理空间),只有子进程修改了其中的某个值时(通常会先调度运行子进程),才会给子进程分配新的物理内存,并根据情况把新的值或原来的值复制给子进程的内存。
- 孤儿进程
- 一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。
- 孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
- 由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
- 僵尸进程
- 一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
- 僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。
- 系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
- 要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。