当前位置: 代码迷 >> 综合 >> (秋招/春招)软件开发/软件测试面试题——操作系统部分
  详细解决方案

(秋招/春招)软件开发/软件测试面试题——操作系统部分

热度:19   发布时间:2023-12-13 00:43:00.0

1.进程与线程的区别,使用场景

区别:

进程是资源分配最小单位,线程是程序执行的最小单位;

进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;CPU切换一个线程比切换进程花费小;

创建一个线程比进程开销小;线程占用的资源要比进程少很多;

线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)

多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);

进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

使用场景:

多线程模型适用于I/O密集型场景,因为I/O密集型场景因为I/O阻塞导致频繁切换,线程只占用栈,程序计数器,一组寄存器等少量资源,切换效率高,单机多核分布式;多进程模型适用于需要频繁的计算场景,多机分布式。

2.进程的状态,进程的调度算法,进程的相互转换

在这里插入图片描述

?进程的状态

通常进程有以下五种状态,前三种是进程的基本状态。

1)运行状态

进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。

2)就绪状态

进程已处于准备运行的状态。即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。

3)阻塞状态(等待状态)

进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

4)创建状态

进程正在被创建,尚未转到就绪状态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息。然后由系统为该进程分配运行时所需的资源。最后把该进程转入到就绪状态。

5)结束状态

进程正从系统中消失,可能是进程正常结束或其他原因中断退出运行。当进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和回收等工作。

?区别就绪状态和等待状态:

就绪状态是指进程仅仅缺少处理机,只要获得处理机资源就能立即执行。等待状态是指进程需要其他资源(除了处理机)或等待某一事件。

之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁的转换到就绪状态的。而其他资源(外设等)的使用和分配或者某一事件的发生(如I/O操作)对应的时间相对来说很长,进程转换到等待状态的次数也相对较少。所以,就绪状态和等待状态是进程生命周期中两个完全不同的状态,需要加以区分。

?进程的相互转换

1)就绪状态->运行状态

处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。

2)运行状态->就绪状态

处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统当中,当有更高优先级的进程处于就绪状态时:调度程序将正在执行的进程转换为就绪状态,让更高优先级的进程执行。

3)运行状态->阻塞状态

当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。

4)阻塞状态->就绪状态

当进程请求的资源可用(被其他进程释放)或者等待的事件到来时,中断处理程序将相应的进程由阻塞状态转换为就绪状态。

?进程的调度算法

1)先来先服务调度算法(FCFS,first come first served)

谁第一个进入队列,谁就被先执行,在它被指向的过程中,不会中断;

2)短作业优先调度算法(SJF,shorest job first)

对预计执行时间短的进程优先分配CPU,通常后来的短进程不会抢占正在执行的进程;这种方式对长进程非常不利,可能长时间得不到执行;

3)最高响应比优先调度算法(HRRN,highest response radio next)

这种方法是对于FCFS和SJF的平衡,FCFS方式只考虑每个作业的等待时间而未考虑作业可能执行时间的长短,而SJF只考虑了执行时间而未考虑等待时间的长短,因此两种算法在某种极端情况下会带来很多问题。

HRRN通过综合这两种情况算出响应比R,根据响应比完成调度。

最高响应比优先算法的优点在于长作业也有机会投入运行,但是缺点在于其每次调度前还需要额外计算响应比。

4)时间片轮转法(RR, Round-Robin)

采用剥夺方式,每个进程被分配一个时间段,按照在队列中的顺序交替进行;其使得每个作业都有机会运行,但是这种方式不利于处理紧急作

3.进程间通信方式,并说出优缺点

?通信方式

1)管道

管道分为有名管道和无名管道。无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

2)信号量

信号量是一个计数器,可以用来控制多个线程对共享资源的访问,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段。

3)信号

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

4)消息队列

消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序。

5)共享内存

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信.

6)套接字:

可用于不同及其间的进程通信

?优缺点:

如果用户传递的信息较少或是需要通过信号来触发某些行为信号机制不失为一种简捷有效的进程间通信方式。

若是进程间要求传递的信息量比较大或者进程间存在交换数据的要求,那就需要考虑别的通信方式了。

无名管道简单方便,但局限于单向通信的工作方式,并且只能在创建它的进程及其子孙进程之间实现管道的共享。

有名管道虽然可以提供给任意关系的进程使用,但是由于其长期存在于系统之中,使用不当容易出错,所以普通用户一般不建议使用。

消息缓冲可以不再局限于父子进程,而允许任意进程通过共享消息队列来实现进程间通信。并由系统调用函数来实现消息发送和接收之间的同步。从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题,使用方便,但是消息队列中信息的复制需要额外消耗CPU的时间。不适宜于信息量大或操作频繁的场合。

共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点,但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的。因此,这些进程之间的读写操作的同步问题操作系统无法实现,必须由各进程利用其他同步工具解决。另外,由于内存实体存在于计算机系统中,所以只能由处于同一个计算机系统中的诸进程共享,不方便网络通信 。

不同的进程通信方式有不同的优点和缺点。因此,对于不同的应用问题,要根据问题本身的情况来选择进程间的通信方式。

4.线程有几种状态,其中处于线程暂停执行的有sleep()和wait(),两者有什么区别?

1)新建状态(New):新创建了一个线程对象。

2)就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3)运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4)阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

(1)等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。

(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

(3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

5.多线程如何保证数据同步

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

?同步方法

即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

代码如:

 public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

?同步代码块

即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

代码如:

synchronized(object){
}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

?使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。

ReentrantLock类的常用方法有:

ReentrantLock() : 创建一个ReentrantLock实例,lock() : 获得锁,unlock() : 释放锁。

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

?使用特殊域变量(volatile)实现线程同步

a.volatile关键字为域变量的访问提供了一种免锁机制,

b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,

c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

?使用局部变量实现线程同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本。副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。变量局部化。

java.lang

类 ThreadLocal

java.lang.Object

java.lang.ThreadLocal

直接已知子类:

InheritableThreadLocal

ThreadLocal 类的常用方法

ThreadLocal() : 创建一个线程本地变量

get() : 返回此线程局部变量的当前线程副本中的值

initialValue() : 返回此线程局部变量的当前线程的"初始值"

set(value) : 将此线程局部变量的当前线程副本中的值设置为value

6.有一台web服务器,你选择用多线程还是多进程,为什么?

1)需要频繁创建销毁的优先用线程

这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的.

2)需要进行大量计算的优先使用线程

所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。这种原则最常见的是图像处理、算法处理。

3)强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程

5)都满足需求的情况下,用你最熟悉、最拿手的方式

7.阻塞非阻塞与同步异步的区别是什么?

?同步与异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。

所谓同步,就是在发出一个“调用”时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由“调用者”主动等待这个“调用”的结果。

而异步则是相反,“调用”在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在“调用”发出后,“被调用者”通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举例子:

你打电话问书店老板有没有《XXX》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。

而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

?阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,

你打电话问书店老板有没有《XXX》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了,当然你也要偶尔过几分钟check一下老板有没有返回结果。

在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

  相关解决方案