一、进程与线程
1、进程
进程是资源分配的基本单位。进程是一段程序执行的过程。
- 进程是一个实体。每个进程都有它自己的地址空间。
- 进程是一个“执行中的程序”。
程序:程序是指令和数据的有序集合。本身没有运行的含义,是一个静态的概念。
2、线程
线程是独立调度的基本单位
一个进程中可以有很多个线程,它们共享进程资源。
QQ和浏览器是两个进程,浏览器进程有很多线程,例如HTTP请求线程,事件响应线程,渲染线程等。线程的并发执行使得浏览器点击一个新链接从而发起HTTP请求,浏览器还可以响应用户的其他事件。
3、区别
- 拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
- 调度:线程是独立调度的基本单位,再同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
- 系统开销:由于创建或者撤销进程,系统都要为之分配或回收资源,如内存空间、I/O设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,再进行线程切换时,涉及当前执行进程CPU环境的保存及新调度进程CPU环境的设置,而线程切换只需要保存和设置少量寄存器内容,开销很小。
- 通信:线程间可以通过直接读写同一进程中地数据进行通信,但是进程通信需要截出IPC。
- 资源管理方式:进程有独立地地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程地不同执行路径。线程有自己地堆栈和局部变量,但是没有单独地地址空间。一个线程死掉等于整个进程死掉
主线程和子线程都有各自独立的pcb。
主线程(由进程退化而来)和子线程
共享:.text .bss .data 堆 动态库加载区 环境变量 命令行参数
通信:全局变量、堆
不共享:栈(如果一共5个线程,栈区被平均分为5份)
线程不要传递局部变量的地址,因为每个线程的栈区都是不稳定的,经常会改变。
4、优缺点
线程执行开销小,但不利于资源的管理和保护;进程正相反。
多线程的优点:
- 无需跨进程边界;
- 程序逻辑和控制方式简单;
- 所有线程可以直接共享内存和变量等;
- 线程方式消耗的总资源比进程方式好
多线程的缺点:
- 每个线程与主线程共用地址空间,受限于2GB地址空间;
- 线程之间的同步与加所控制比较麻烦;
- 一个线程崩溃可能影响整个程序的稳定性
- 到达一定线程数的程度后,即使再增加CPU也无法提高性能。
- 线程能够提高的总性能有限,而且线程多了,其本身调度也颇为麻烦,需要消耗比较多的CPU
多进程的优点:
- 每个进程相互独立,不影响主程序的稳定性,子进程崩溃不会影响其他子进程。
- 通过增加CPU,就可以容易的扩充性能。
- 可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系。
- 每个子进程都有2GB地址空间个相关资源,总体能达到的性能上限非常大
多进程的缺点:
- 逻辑控制复杂,需要和主程序交互;
- 需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算
- 多进程调度开销比较大
5、通信方式
进程
- 管道(pipe):半双工,单向流动,只能在具有亲缘关系的进程使用
- 有名管道(namepipe):半双工,允许无亲缘关系进程通信
- 信号量(semphore):控制多个进程对共享资源的访问,常作为锁机制
- 消息队列(messagequeue):消息队列。克服了信号传递信息少,管道只能承载无格式字节流和缓冲区大小受限
- 信号(signal):用于通知接收进程某个事件已经发生
- 共享内存(shared memory):映射一段能够被其他进程访问的内存
- 套接字(socket):可用于不同及其间的进程通信
线程
- 锁机制:包括互斥锁、条件变量、读写锁
- 互斥锁提供了以排他方式防止数据结构被并发修改的方法
- 读写锁允许多个线程同时读共享数据,但对写操作是互斥的
- 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真。与互斥锁一起使用,对条件的测试是在互斥锁的保护下进行的。
- 信号量机制(Semaphore)
- 信号机制(Signal)
二、进程状态的切换
三、进程调度算法
1、先来先服务和端作业有限调度算法
-
先来先服务 first-come first-served(FCFS)
适用场景:比较利于长作业,因为长作业会长时间占据处理机。有利于CPU繁忙的作业,而不利于I/O繁忙的作业 -
短作业优先 shortest job first(SJF)主要不足之处式长作业的运行得不到保证
-
最短剩余时间优先 shorted remaining time next (SRTN)
2、高优先权优先调度算法
- 优先级调度(避免低优先级永远等不到调度,可随时间推一增加等待进程的优先级)
- 非抢占式优先权算法:一旦处理机分配给就绪队列优先权最高的进程,便会一直执行直至完成(主要用于批处理系统,也可用于对实时性要求不高的实时系统中)
- 抢占式优先权算法:若出现优先权更高的进程,进程调度程序立即停止当前进程,重新分配给优先权最高的进程。(常用于要求严格的实时系统中,以及对性能要求较高的批处理和分时系统中)
- 高响应比有限调度算法:引入动态优先权,优先级随时间增加而以速率a提高。
2、基于时间片的轮转调度算法
- 时间片轮转(FCFS原则排成队列,每次分配时间片给队首,队首进程完了送到末尾)
优点式兼顾长短作业;缺点是平均等待时间较长,上下文切换费时,适用于分时系统。 - 多级反馈队列(FCFS和时间片轮转的结合)
3、实时系统
硬实时:满足绝对的截止时间
软实时:可容忍一定的超时
三、进程同步问题
1、生产者和消费者问题
问题描述:一组生产者进程和一组消费者进程共享一块初始为空,大小确定的缓冲区,只有当缓冲区不为满时,生产者进程才可以把信息放入缓冲区,否则就要等待;只有缓存区不为空时,消费者进程才能从中取出消息,否则就要等待。缓冲区一次只能一个进程访问(临界资源)。
问题分析:生产者与消费者进程对缓冲区的访问是互斥关系,而生产者与消费者本身又存在同步关系,即必须生成之后才能消费。因而对于缓冲区的访问设置一个互斥量,再设置两个信号量一个记录空闲缓冲区单元,一个记录满缓冲区单元来实现生产者与消费者的同步。
伪代码:
semaphore mutex=1;
semaphore full=0; //满缓冲区单元
semaphore empty=N; //空闲缓冲区单元
prodecer()
{
while(1)
{
P(empty);
P(mutex);
add_source++;
V(mutex);
V(full);
}
}
consumer()
{
while(1)
{
P(full);
P(mutex);
add_source--;
V(mutex);
V(empty);
}
}
2、读者和写者问题
问题描述:有读者与写者两个并发进程共享一个数据,两个或以上的读进程可以访问数据,但是一个写者进程访问数据与其他进程都互斥。
问题分析:读者与写者是互斥关系,写者与写者是互斥关系,读者与读者是同步关系。因而需要一个互斥量实现读与写和写与写互斥,一个读者的访问计数和实现对计数的互斥。
问题解决:三种伪代码实现
1、读者优先
读者优先,只要有读者源源不断,写者就得不到资源。容易造成写者饥饿。
//读者优先
int count=0;
semaphore mutex=1; //读者计数锁
semaphore rw=1; //资源访问锁
//写者
writer()
{
while(1)
{
P(rw);
writing sth;
V(rw);
}
}
//读者
reader()
{
while(1)
{
P(mutex);
if(count==0)
P(rw);
count++;//读者计数
V(mutex);
reading sth;
P(mutex);
count--;
if(count==0)
V(rw);
V(mutex);
}
}
2、读写公平
读者与写者公平抢占资源,但是只要之前已经排队的读者,就算写者获取到资源,也要等待所有等待的读者进程结束。
//读写公平
int count=0;
semaphore mutex=1; //读者计数锁
semaphore rw=1; //资源访问锁
semaphore w=1; //读写公平抢占锁
writer()
{
while(1)
{
P(w);
P(rw);
writing sth;
V(rw);
V(w);
}
}
reader()
{
while(1)
{
P(w);
P(mutex);
if(count==0)
P(rw);
count++;
V(mutex);
V(w);
reading sth;
P(mutex);
count--;
if(count==0)
V(rw);
V(mutex);
}
}
3、写者优先
写者优先,只要写者源源不断,读者就得不到资源,但是在这之前已经排队的的读者进程依然可以优先获得资源,在这之后则等待所有写者进程的结束。这种也易造成读者饥饿。
//写者优先
int write_count=0; //写计数
int count=0; //读计数
semaphore w_mutex=1; //读计数时锁
semaphore r_mutex=1; //写计数时锁
semaphore rw=1; //写优先锁
semaphore source=1; //资源访问锁
writer()
{
while(1)
{
P(w_mutux);
if(write_count==0)
P(rw); //获得则只要有写进程进来就不释放
write_count++;
V(w_mutux)
P(resouce); //写时互斥必须加资源独占的锁
writing sth;
V(resouce);
P(w_mutux);
write_count--;
if(write_count==0)
V(rw);
V(w_mutux);
}
}
reader()
{
while(1)
{
P(rw); //使用了立即释放
P(r_mutex);
if(count==0)
P(resouce);
count++;
V(r_mutex);
V(rw);
reading sth;
P(r_mutex);
count--;
if(count==0)
V(resouce);
V(r_mutex);
}
}
2、哲学家就餐问题
问题描述:一张圆桌上坐着五名哲学家,每两名哲学家之间的桌子摆一根筷子,哲学家只有同时拿起左右两根筷子时才可以用餐,用餐完了筷子放回原处。
问题分析:这里五名哲学家就是五个进程,五根筷子是需要获取的资源。可以定义互斥数组用于表示五根筷子的互斥访问,为了防止哲学家个取一根筷子出现死锁,需要添加一定的限制条件。一种方法是限制仅当哲学家左右筷子均可以用时,才拿起筷子,这里需要一个互斥量来限制获取筷子不会出现竞争。
问题解决:一次仅能一个哲学家拿起筷子,效率比较低。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;
pi()
{
while(1)
{
P(mutex);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(mutex);
eating;
V(chopstick[i]);
V(chopstick[(i+1)%5]);
}
}