进程与线程学习笔记
1. 进程
1.1 进程概述
一个进程(process)是一个正在执行程序的实例,包括程序计数器、寄存器和当前值。每个进程有一个自己的虚拟CPU,几个虚拟的CPU并行运算,而事实上真正的CPU是在个进程之间切换的。假设有4个进程,各自控制自己的运行流程(逻辑计数器),在每个进程运行时,把逻辑计数器装入实际计数器中,结束运行时,则将实际计数器的内容装入逻辑计数器中。也就是说,一个CPU一次只能运行一个进程。
守护进程(daemon):运行在后台的进程,通常处于睡眠状态,但是可能被唤醒。如电子邮件进程,当一个邮件到达时被唤醒接收邮件。
1.2 进程与程序的类比
程序——菜谱
进程——做菜
进程切换——有其他紧急事务处理时,记录菜做到哪一步,先处理紧急事务,然后从离开时的那一步做起。
进程不可以在计算机之间迁移而程序可以
1.3 进程创建与终止
导致进程创建的事件:1)系统初始化;2)正在运行的进程进行系统调用;3)用户请求创建;4)一个批处理作业的初始化。
导致进程终止的时间:1)正常退出(自愿);2)出错退出(自愿);3)严重错误(非自愿);4)被其它进程杀死(非自愿)
1.4 进程的层次结构
父进程与子进程:Unix中,进程和其所有子女及后裔共同组成一个进程组,树结构;windows没有进程层次的概念,所有进程地位相同。而Windows中一个类似的操作是在创建进程时,父进程得到一个句柄用来控制子进程,并且他可以把这个句柄传送给别的进程,而UNIX中的进程不能剥夺其子女的继承权。
1.5 进程状态
三状态:1)运行态(Running):该时刻进程实际占用CPU;2)就绪态(Ready):可运行,但因其它进程正在运行而暂时停止;3)阻塞态(Blocked):由于某种原因即使CPU空闲也不能运行,直到这个原因消除。
进程挂起(Suspend):优先级的引入会使有些进程等待时间过长,从而会被换至外存,成为进程挂起。进程挂起分为阻塞挂起和就绪挂起。就绪挂起一旦进入内存即可执行。
1.6 进程实现
动态性;独立性(个进程地址空间相互独立);并发性;异步性;结构化(代码段、数据段、栈段)
2. 线程
2.1 使用多线程的原因
1)并行实体共享地址空间和可用数据的能力;2)线程比进程更轻量级,容易创建和撤销;3)性能高,当存在大量计算和大量IO处理时,多线程允许这些活动彼此重叠进行;4)多CPU系统中,真正的并行有了实现的可能。
例子:编辑一个很长的文档
在第1页删除一句话,然后想跳转到第600页修改。若不是多线程的话,用户需要等很长时间,等文档格式化完之后才能显示出来;而如果一个线程a处理格式化,一个线程b处理与用户交互的话,等用户删除一句话之后,a即刻进行格式化处理,当用户请求查看第600页时,可能已经格式化完成,线程b立即显示第600页。还可以加一个线程c用来隔段时间保存文档,若程序是单线程的话,在保存文档的时候,鼠标键盘都不会响应,性能很差。
在这里,由于在同一个文件上操作,三个不同的进程是不能同时工作的,而线程共享数据和内存,可以同时访问同一个文件。
另外一个例子是一个多线程的Web服务器(具体模型见《现代操作系统》P55页)
2.2 线程
线程(thread)拥有一个程序计数器(记录执行到哪一个命令)、寄存器(保存当前的工作变量)和一个堆栈(记录执行历史)。线程状态与与进程状态类似。
3. 进程间通信
竞争条件(race condition):两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序。
临界区(critical region/critical section):进行访问共享内存的程序片段。
互斥(mutual exclusion),即以某种手段确保当一个进程在使用一个共享变量或文件时,其它进程不能做同样的操作。
忙等待的互斥方案(不成熟的):
①屏蔽中断:在每个进程刚刚进入临界区后屏蔽所有中断。若没有打开中断,则产生问题,危险做法!
②锁变量:没有解决根本问题
③严格轮换法:自旋锁(spin lock)用于忙等待的锁。进程0、1轮流使用临界区,顺序必须严格按照010101...如果顺序为01000...则会有问题。
④Peterson解法
⑤TSL指令
这些方法有一个共同点:忙等待。其基本思想都是当一个进程想进入临界区时,先检查是否允许进入,若不允许,则该进程原地等待直到被允许。这些方法都有优先级反转问题(priority inversion problem)—— 两个进程H(优先级高)L(优先级低),在某一时刻,L处于临界区中,此时H转为就绪态,开始忙等待,而当H就绪时L不会被调度,也就无法离开临界区,所以H将永远忙等待下去。
睡眠与唤醒原语的互斥方案(也是不成熟的):
以生产者消费者(生产者将信息放入缓冲区,消费者从缓冲区中取走信息)问题为例(一个producer和一个consumer)
1 #define N 100 //缓冲区槽数 2 int count=0; //缓冲区中数据项数目 3 4 void producer(void) 5 { 6 int item; 7 while(true) 8 { 9 item = produce_item();//产生数据项 10 if(count == N) sleep(); //缓冲区满,去睡觉 11 insert_item(item); //想缓冲区中放入新的数据项 12 count++; 13 if(count == 1) wakeup(consumer);//缓冲区空时,在生产新数据后唤醒消费者 14 } 15 } 16 17 void consumer(void) 18 { 19 int item; 20 while(true) 21 { 22 if(count == 0) sleep(); //缓冲区空,去睡觉 23 item = remove_item; //缓冲区中取出数据项 24 count--; 25 if(count == N - 1) wakeup(producer);//缓冲区不满了唤醒生产者 26 consume_item(item);//打印数据项 27 } 28 } 29
在这里,由于没有对count加以限制,可能会出现竞争。consumer和producer并发执行,假如count为0了,consumer判定其为0但是还没有执行sleep();此时,producer刚刚生产一个数据放入缓冲区然后“唤醒”consumer;然后consumer执行了sleep(),此时producer实际上并没有唤醒consumer,结果就会只生产不消费,最后producer也sleep了。
(以下部分暂时没写完,明天继续~~)
竞争避免的方法(解决共享资源竞争):
①信号量(semaphore):PV原语
②互斥量(mutex)
③消息传递(message passing)
进程通信手段:管程(monitor)