• 正文
    • 那薪资待遇如何呢?
    • 具体的面试题如下:
    • 中国电信(一面)
    • 中国电信(二面)
  • 相关推荐
申请入驻 产业图谱

中国电信一面二面直接秒了 附:面试题+解析

03/27 11:00
937
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

图解学习网站:https://xiaolincoding.com

大家好,我是小林。

有同学问我有没有写过电信的面经?我翻了一下我的记录,发现还没写过中国电信的面经,但是写过中国移动的面经。

中国电信、移动和联通是国内三大运营商了,都属于国企,每年也是一样都有开发岗位的招聘需求,国企的优势就是加班不多,相比互联网公司能早下班,而且也更加稳定一些。

那薪资待遇如何呢?

中国电信给校招软开岗的总包是 20 万-25 万(一线城市),二线城市的话是 15 万左右,不过总包是算上各种福利总和(比如奖金、公积金啥的),每个月实际到手会比私企 20w 总包的低一些。

之前训练营也有校招同学拿到一线城市中国电信 offer,开的是总包 21w。

二线城市的中国电信开的是 15w 总包

这次跟大家聊聊中国移动的软开一二面面经,中国移动面试流程是 2 轮面试,面试的时长都不算长:

    第一轮技术面面了 30 分钟,拷打了操作系统、网络、Redis、Java 方面的知识第二轮则是技术面+hr 面一起了,时长 20 分钟,技术问的问题比多,主要是拷打了 操作系统+数据结构方面的内容,剩下了就是聊天局了。

具体的面试题如下:

中国电信(一面)

1. 信号了解吗,平时接触过哪些信号?

信号是进程间通信的一种方式,它是操作系统向进程发送的一种异步通知机制,用于告知进程发生了某个特定事件。进程可以根据接收到的信号采取相应的行动,如暂停执行、继续执行、终止进程等。

平时接触过的信号有:

SIGINT(中断信号)

    • :通常由用户按下 Ctrl+C 组合键产生,用于通知进程需要中断当前的操作并终止运行。例如,在命令行中运行一个程序时,按下 Ctrl+C 可以向该程序进程发送 SIGINT 信号,使其停止运行。

SIGTERM(终止信号)

    • :这是一种正常的终止信号,用于通知进程优雅地结束运行。与 SIGINT 不同,SIGTERM 通常由系统或其他进程发送,而不是由用户直接触发。进程接收到 SIGTERM 信号后,应该进行一些清理工作,如关闭打开的文件、释放资源等,然后再终止。许多服务程序在接收到 SIGTERM 信号后会逐步停止正在处理的任务,并关闭连接,以确保系统的稳定性和数据的完整性。

SIGKILL(强制终止信号)

    :这是一种强制终止进程的信号,不允许进程进行任何清理操作,直接将进程终止。通常在进程出现严重错误或无法正常终止时使用。例如,当一个进程陷入死循环或占用大量系统资源且无法通过其他方式停止时,可以使用 SIGKILL 信号来强制结束该进程。不过,由于 SIGKILL 信号不会给进程留下清理资源的机会,可能会导致一些资源泄漏或数据不一致的问题,所以应该谨慎使用。

2. 操作系统中,进程同步与数据传输有什么方法,各自有什么特点?

进程同步的方法有下面这些:

信号量

    • :信号量是一个整数变量,用于控制多个进程对共享资源的访问。它可以实现更复杂的同步策略,如控制并发访问的进程数量。当信号量的值大于 0 时,进程可以获取信号量并访问资源,同时信号量的值减 1;当信号量的值为 0 时,进程需要等待。信号量可以分为二进制信号量(只能取 0 和 1)和计数信号量(可以取任意非负整数)。优点是能灵活控制资源的并发访问,适用于多种复杂的同步场景。缺点是如果使用不当,也可能出现死锁或资源分配不合理的情况。

互斥锁

    • :互斥锁是一种简单的进程同步机制。当一个进程访问共享资源时,它会获取互斥锁,其他进程则必须等待,直到该进程释放锁。这种方法能确保在任何时刻只有一个进程可以访问共享资源,实现了对资源的互斥访问。优点是实现简单,适用于对少量资源的短期锁定。缺点是如果一个进程在持有锁期间出现异常或长时间不释放锁,可能导致其他进程长时间等待,甚至出现死锁。

条件变量:

    条件变量通常与互斥锁一起使用,用于实现进程间的条件等待。一个进程可以在某个条件满足时通过条件变量通知其他等待的进程。它允许进程在等待特定条件发生时阻塞,避免了忙等待,从而提高了系统的效率。优点是能有效实现进程间的协作,减少不必要的 CPU 开销。缺点是需要与互斥锁配合使用,编程相对复杂,且对条件的判断和通知机制需要谨慎处理,否则可能导致程序出现逻辑错误。

进程传输数据方法:

管道:

    • 管道是一种半双工的通信方式,用于在具有亲缘关系的进程之间传输数据。通常由父进程创建管道,然后 fork 出子进程,子进程和父进程通过管道进行数据传输。数据在管道中是单向流动的,一端用于写入数据,另一端用于读取数据。管道的优点是简单易用,能方便地实现进程间的通信。缺点是只能在有亲缘关系的进程间使用,且半双工的特性限制了数据传输的灵活性,同时管道的容量有限,如果写入速度过快可能导致数据堵塞。

消息队列:

    • 消息队列是一种异步的通信机制,进程可以向消息队列中发送消息,其他进程可以从消息队列中读取消息。消息队列中的消息具有一定的格式和优先级,不同进程可以根据自己的需求从队列中获取消息。它允许进程在不同的时间进行通信,不需要实时等待对方的响应。优点是通信具有异步性和松耦合性,适用于不同步执行的进程间通信。缺点是消息的大小和队列的长度可能受到系统限制,而且如果消息处理不及时,可能导致队列溢出。

共享内存:

    • 共享内存是一种高效的进程间通信方式,它允许多个进程共享同一块内存区域。进程可以直接读写共享内存中的数据,无需进行数据的复制。这种方式大大提高了数据传输的效率,尤其适用于大量数据的共享。但是,由于多个进程可以同时访问共享内存,因此需要配合同步机制(如互斥锁、信号量等)来保证数据的一致性和完整性。优点是数据传输效率高,能快速共享大量数据。缺点是需要复杂的同步机制来确保数据的正确性,编程难度较大,且对共享内存的访问控制需要谨慎处理,否则可能导致数据混乱。

套接字:

    套接字是一种网络通信机制,不仅可以用于不同主机上的进程间通信,也可以用于同一主机上的进程间通信。它提供了一种基于网络协议的通信方式,支持多种通信协议,如 TCPUDP。通过套接字,进程可以发送和接收数据报,实现双向的数据传输。套接字的优点是具有很强的通用性和灵活性,能适应不同的网络环境和通信需求。缺点是网络通信存在一定的延迟和不确定性,而且需要处理网络故障、数据丢失等问题,编程实现相对复杂。

3. 了解过哪些网络协议?

4. 说一下https加密过程?

传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的,在将 TLS 证书部署服务端时,证书文件其实就是服务端的公钥,会在 TLS 握手阶段传递给客户端,而服务端的私钥则一直留在服务端,一定要确保私钥不能被窃取。

在 RSA 密钥协商算法中,客户端会生成随机密钥,并使用服务端的公钥加密后再传给服务端。根据非对称加密算法,公钥加密的消息仅能通过私钥解密,这样服务端解密后,双方就得到了相同的密钥,再用它加密应用消息。

我用 Wireshark 工具抓了用 RSA 密钥交换的 TLS 握手过程,你可以从下面看到,一共经历了四次握手:

TLS 第一次握手

首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。在这一步,客户端主要向服务器发送以下信息:

    (1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。(2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。(3)客户端支持的密码套件列表,如 RSA 加密算法。

TLS 第二次握手

服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:

    (1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。(3)确认的密码套件列表,如 RSA 加密算法。(4)服务器的数字证书。

TLS 第三次握手

客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。

如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:

    (1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。

上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。

服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」

TLS 第四次握手

服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。

然后,向客户端发送最后的信息:

    (1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

5. 使用缓存时可能会产生什么问题,如何解决?

    • 可能会出现缓存与数据库数据不一致的问题,导致业务逻辑错误,比如更新数据库后,缓存未同步或同步失败。解决方式通过Canal监听数据库Binlog,异步更新缓存,保证最终一致性。热点 key 问题,某个Key的访问量极高,导致缓存服务器单点压力过大,解决方式将一个热点Key拆分为多个子Key(如

hot_key:1

hot_key:2

    ),分散到不同节点。大 key 的问题,单个Key存储的数据过大(如10MB的List),导致网络阻塞、查询缓慢,解决方式将大Key拆分为多个小Key,或使用压缩算法(如Gzip)减少体积。缓存异常的问题,比如缓存击穿、穿透、雪崩的问题

6. 缓存击穿,穿透,雪崩是什么,如何预防?

缓存雪崩:当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

缓存击穿:如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。


缓存穿透:当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

缓存雪崩解决方案:

均匀设置过期时间:如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。

互斥锁:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新

缓存击穿解决方案:

    互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

缓存穿透解决方案:

    非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。缓存空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。布隆过滤器:我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

7. 有一组服务器,现在来了一个任务,希望实现一个均衡调度算法,有什么思路?

轮询调度

    • :依次将任务分配给每台服务器,简单公平,但未考虑服务器负载差异

加权轮询

    • :根据服务器性能分配不同权重,高性能服务器获得更多任务,缺点权重值的设置需要根据服务器的实际情况进行调整,且难以动态适应服务器负载的变化。

最少连接

    • :将新任务分配给当前连接数最少的服务器,这样可以确保任务被分配到相对空闲的服务器上,避免某些服务器过载

加权最少连接算法

    :加权最少连接算法结合了加权轮询算法和最少连接算法的优点。在考虑服务器当前连接数的同时,还考虑了服务器的性能权重。计算每台服务器的 “加权连接数”(通常为当前连接数除以权重值),将新任务分配给加权连接数最小的服务器。

8. Java的容器了解吗,用过哪些,适用的场景?

List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。

    ArrayList 是容量可变的非线程安全列表,其底层使用数组实现。当发生扩容时,会创建更大的数组,并把原数组复制到新数组。ArrayList支持对元素的快速随机访问,但插入与删除速度很慢。LinkedList本质是一个双向链表,与ArrayList相比,其插入和删除速度更快,但随机访问速度更慢。Vector 与 ArrayList 类似,底层也是基于数组实现,特点是线程安全,但效率相对较低,因为其方法大多被 synchronized 修饰

Map 是一个键值对集合,存储键、值和之间的映射。Key 无序,唯一;value 不要求有序,允许重复。Map 没有继承于 Collection 接口,从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。主要实现有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap

    HashMap:JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突),JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。HashTable:数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的,HashTable 是线程安全的,其方法使用 synchronized 修饰,且不允许 null 键和 null 值。TreeMap:红黑树(自平衡的排序二叉树)实现,Key 按自然顺序或 Comparator 排序。ConcurrentHashMap:采用 Node 数组 + 链表 + 红黑树实现,是线程安全的。JDK1.8 以前使用 Segment 锁(分段锁),JDK1.8 以后使用 volatile + CAS 或者 synchronized 保证线程安全。其中,Segment 锁是将整个数据结构分成多个段,每个段有独立的锁,不同段的操作可以并发进行;volatile 保证变量的可见性,CAS 是一种无锁算法,synchronized 用于对链表或红黑树进行加锁操作。

Set不允许存在重复的元素,与List不同,Set中的元素是无序的(TreeSet 除外)。常用的实现有HashSet,LinkedHashSet和TreeSet。

    HashSet通过HashMap实现,HashMap的Key即HashSet存储的元素,所有Key都是用相同的Value,一个名为PRESENT的Object类型常量。使用Key保证元素唯一性,但不保证有序性。由于HashSet是HashMap实现的,因此线程不安全。LinkedHashSet继承自HashSet,通过LinkedHashMap实现,使用双向链表维护元素插入顺序。TreeSet通过TreeMap实现的,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。

9. Java的垃圾回收机制,你知道哪些?

标记-清除算法

    • :标记-清除算法分为“标记”和“清除”两个阶段,首先通过可达性分析,标记出所有需要回收的对象,然后统一回收所有被标记的对象。标记-清除算法有两个缺陷,一个是效率问题,标记和清除的过程效率都不高,另外一个就是,清除结束后会造成大量的碎片空间。有可能会造成在申请大块内存的时候因为没有足够的连续空间导致再次 GC。

复制算法

    • :为了解决碎片空间的问题,出现了“复制算法”。复制算法的原理是,将内存分成两块,每次申请内存时都使用其中的一块,当内存不够时,将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。复制算法解决了空间碎片的问题。但是也带来了新的问题。因为每次在申请内存时,都只能使用一半的内存空间。内存利用率严重不足。

标记-整理算法

    • :复制算法在 GC 之后存活对象较少的情况下效率比较高,但如果存活对象比较多时,会执行较多的复制操作,效率就会下降。而老年代的对象在 GC 之后的存活率就比较高,所以就有人提出了“标记-整理算法”。标记-整理算法的“标记”过程与“标记-清除算法”的标记过程一致,但标记之后不会直接清理。而是将所有存活对象都移动到内存的一端。移动结束后直接清理掉剩余部分。

分代回收算法

    :分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老年代。

10. 项目和其他

    介绍项目经历,比较大的挑战,如何解决项目里有哪些优化,后续有什么可以深一步拓展的未来的职业规划反问

中国电信(二面)

1. 一个数组,原地建堆,时间复杂度?

原地建堆的时间复杂度为 O(n),具体分析如下。

假设数组的长度为 n,且堆是一棵完全二叉树,节点总数与高度关系

设堆的高度为h,树的层数为h = log₂n。第k 层的节点数为n/(2^k),该层每个节点需要最多调整k 次(从下往上调整)。

总调整次数计算,公式如下:

等比数列求和结果为 O(2n),因此 T(n) = O(n)

不同层级的调整次数

高度为1 的节点:调整次数为1 × n/2

高度为2 的节点:调整次数为2 × n/4。总和为n/2 + n/4 + n/8 + ... ≈ 2nO(n)

Java 原地建堆的算法实现如下:

public class HeapBuilder {
    // 下沉操作(以最大堆为例)
    private static void siftDown(int[] arr, int i, int n) {
        while (i < n) {
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            int largest = i;

            if (left < n && arr[left] > arr[largest]) {
                largest = left;
            }
            if (right < n && arr[right] > arr[largest]) {
                largest = right;
            }

            if (largest != i) {
                // 交换元素
                int temp = arr[i];
                arr[i] = arr[largest];
                arr[largest] = temp;
                i = largest;  // 继续下沉
            } else {
                break;  // 调整完成
            }
        }
    }

    // 原地建堆
    public static void buildHeap(int[] arr) {
        int n = arr.length;
        // 从最后一个非叶节点开始反向遍历
        for (int i = n / 2 - 1; i >= 0; i--) {
            siftDown(arr, i, n);
        }
    }

    public static void main(String[] args) {
        int[] arr = {3, 5, 1, 7, 2, 9, 4};
        buildHeap(arr);  // 结果: [9,7,4,5,3,1,2]
    }
}

2. 操作系统的进程调度算法有哪些

先来先服务调度算法

最简单的一个调度算法,就是非抢占式的先来先服务算法了。

顾名思义,先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。

这似乎很公平,但是当一个长作业先运行了,那么后面的短作业等待的时间就会很长,不利于短作业。 FCFS 对长作业有利,适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。

最短作业优先调度算法

最短作业优先调度算法同样也是顾名思义,它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。

这显然对长作业不利,很容易造成一种极端现象。

比如,一个长作业在就绪队列等待运行,而这个就绪队列有非常多的短作业,那么就会使得长作业不断的往后推,周转时间变长,致使长作业长期不会被运行。

高响应比优先调度算法

前面的「先来先服务调度算法」和「最短作业优先调度算法」都没有很好的权衡短作业和长作业。

那么,高响应比优先调度算法主要是权衡了短作业和长作业。

每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行,「响应比优先级」的计算公式:

从上面的公式,可以发现:

    如果两个进程的「等待时间」相同时,「要求的服务时间」越短,「响应比」就越高,这样短作业的进程容易被选中运行;如果两个进程「要求的服务时间」相同时,「等待时间」越长,「响应比」就越高,这就兼顾到了长作业进程,因为进程的响应比可以随时间等待的增加而提高,当其等待时间足够长时,其响应比便可以升到很高,从而获得运行的机会;

时间片轮转调度算法

最古老、最简单、最公平且使用最广的算法就是时间片轮转调度算法

每个进程被分配一个时间段,称为时间片,即允许该进程在该时间段中运行。

    如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配另外一个进程;如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;

另外,时间片的长度就是一个很关键的点:

    如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率;如果设得太长又可能引起对短作业进程的响应时间变长。将

通常时间片设为 20ms~50ms 通常是一个比较合理的折中值。

最高优先级调度算法

前面的「时间片轮转算法」做了个假设,即让所有的进程同等重要,也不偏袒谁,大家的运行时间都一样。

但是,对于多用户计算机系统就有不同的看法了,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级调度算法。 进程的优先级可以分为,静态优先级或动态优先级:

      • 静态优先级:创建进程时候,就已经确定了优先级了,然后整个运行时间优先级都不会变化;动态优先级:根据进程的动态变化调整优先级,比如如果进程运行时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是

    随着时间的推移增加等待进程的优先级

该算法也有两种处理优先级高的方法,非抢占式和抢占式:

    非抢占式:当就绪队列中出现优先级高的进程,运行完当前进程,再选择优先级高的进程。抢占式:当就绪队列中出现优先级高的进程,当前进程挂起,调度优先级高的进程运行。

但是依然有缺点,可能会导致低优先级的进程永远不会运行。

多级反馈队列调度算法

多级反馈队列调度算法是「时间片轮转算法」和「最高优先级算法」的综合和发展。

顾名思义:

    「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;

来看看,它是如何工作的:

设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短;新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成;当较高优先级的队列为空,才调度较低优先级的队列中的进程运行。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行;

可以发现,对于短作业可能可以在第一级队列很快被处理完。

对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。

3. 进程,线程,协程是什么?

首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己的独立内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的开销也较大,因为需要保存和恢复整个进程的状态。

接下来是线程。线程是进程内的一个执行单元,也是CPU调度和分派的基本单位。与进程不同,线程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。

最后是协程。协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,协程需要程序员显式地进行调度和管理,相对于线程和进程来说,其编程模型更为复杂。

 

点赞
收藏
评论
分享
加入交流群
举报

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录