嵌入式 操作系统(四)

目录:

1.线程池设计思路

2.请你说说什么是孤儿进程、僵尸进程、守护进程?

3.什么是并发,同步,异步,互斥,阻塞,非阻塞?

4.你了解死锁吗?请你说说产生的条件?如何解决?

5.什么是互斥锁机制?互斥锁和读写锁有什么区别?

6.什么是自旋锁,和互斥锁有什么区别?

内容:

1.线程池设计思路

设计线程池的原因

  • 减少线程创建和销毁的开销
  • 控制线程数量,避免资源浪费
  • 线程池中的线程是预先创建好的,任务一到达,线程池就能立刻分配线程来执行任务,极大地提高了任务响应的速度。
  • 线程池可以设置任务的优先级,优先执行高优先级的任务,适用于任务有不同重要性的场景。
  • 线程池的管理机制有助于避免线程泄漏。没有线程池时,线程的创建和销毁通常是由程序员手动管理的,容易出现线程未关闭或无法结束的情况,导致线程泄漏,占用系统资源。

如何设计一个线程池

  • 创建一个线程安全的任务队列,充当生产者消费者队列,作为临界资源。
  • 初始化n个线程,并让它们进入等待状态,准备处理任务。
  • 当任务队列为空时,所有线程阻塞,等待新任务的到来。
  • 当生产者队列来一个任务之后,先对队列进行加锁,然后把任务挂到队列上。使用条件变量通知一个阻塞中的线程来处理任务。

线程池中线程数量

注意:线程池中线程数量与CPU核数、IO、并行、并发有关。

  • CPU密集型任务:对于 CPU 密集型任务,建议将线程池大小设置为CPU核心数量+1,因为 CPU 密集型任务不会并行执行得更快,反而会受到线程切换和上下文切换的影响。这样可以保持CPU的高利用率。
  • I/O密集型任务:对于 I/O 密集型任务,线程池的大小可以设置得更大,因为这些任务通常会在等待 I/O 操作时阻塞,因此可以通过增加线程数来更好地利用系统资源。,建议将线程池大小设置为2*CPU核心数量+1。
  • 并行度和响应性需求:线程池的大小还受到任务的并行度和对响应性的需求影响。最佳线程数目 = (线程等待时间/线程CPU时间 + 1)* CPU数目。

2.请你说说什么是孤儿进程、僵尸进程、守护进程?

孤儿进程

孤儿进程是指一个子进程的父进程在子进程还未结束时已经终止了。这意味着子进程的父进程消失了,而该子进程仍然在运行。

为什么会出现孤儿进程

  • 当一个进程创建了子进程(即父进程是子进程的父进程),如果父进程提前终止(无论是正常退出还是崩溃),子进程就会成为孤儿进程。
  • 操作系统为了避免孤儿进程影响系统稳定性,通常会将这些孤儿进程的父进程设置为 init进程(在 UNIX/Linux 系统中是进程号为1的进程)。init进程将会收养这些孤儿进程,成为它们的父进程,并负责清理资源。

例子:

假设进程 A 创建了进程 B。如果进程 A 退出,而进程 B 还在运行,那么进程 B 就变成了孤儿进程。此时,init进程会成为进程 B 的父进程。

僵尸进程

僵尸进程是指已经完成执行的进程,但其父进程还没有调用 wait()waitpid() 来回收该进程的资源,因此它仍然保留在进程表中。

为什么会出现僵尸进程

  • 当一个进程完成执行后,操作系统会保留该进程的部分信息(如进程ID、退出状态等)以供父进程获取。如果父进程没有及时调用 wait() 来获取子进程的退出状态,子进程的资源就无法被释放。
  • 虽然僵尸进程的执行已完成,但它依然占据操作系统的进程表项。

检查和清理僵尸进程

使用 pstop 查找僵尸进程

  • 使用命令 ps aux | grep 'Z' 来查找系统中所有的僵尸进程。Z 表示该进程是僵尸进程。
ps aux | grep 'Z'

  • 使用 top 命令检查僵尸进程。在 top 中,僵尸进程通常显示为 Z 状态。

杀死父进程

如果父进程没有回收子进程的资源,可以通过终止父进程来让 init 进程收养子进程,然后由 init 进程回收资源。

  • 查找父进程的PID:使用 ps -ef 查找僵尸进程的父进程 ID(PID)。
  • 杀死父进程:使用 kill 命令终止父进程。
kill -9 <parent_pid>

一旦父进程被终止,init 进程会收养子进程并回收它们,僵尸进程就会被清除。

例子:

假设进程 A 创建了进程 B。进程 B 执行完成,但父进程 A 没有调用 wait()。此时,进程 B 就变成了僵尸进程。直到进程 A 调用 wait() 或退出后,init 进程回收 B,僵尸进程才会消失。

守护进程

守护进程是在后台运行不受终端控制的进程(如输入、输出等)。网络服务大部分就是守护进程。

为什么需要守护进程

  • 守护进程通常在系统启动时启动,且一直运行,直到系统关闭。它们提供了服务,通常是系统的核心功能服务,如网络服务、日志管理、系统监控等。
  • 守护进程通常不需要用户交互,而是自动化地执行某些任务。

守护进程的创建过程:

  • 创建子进程:一个进程调用 fork() 来创建子进程。
  • 子进程脱离终端:子进程通过调用 setsid() 来创建一个新的会话,并使自己成为会话的领导者,脱离终端。
  • 关闭文件描述符:守护进程会关闭从父进程继承的文件描述符,避免它们与终端、标准输入/输出产生关联。
  • 在后台运行:守护进程开始执行其后台任务,并将自己的输出定向到日志文件等地方。

例子:

  • sshd:在 Linux 系统中,sshd(SSH 守护进程)负责处理远程登录连接。它会在系统启动时启动,并保持在后台运行,等待用户通过 SSH 协议连接。
  • croncron 守护进程定期执行系统任务(如定时备份、清理日志等)。

3.什么是并发,同步,异步,互斥,阻塞,非阻塞?

并发

并发指的是在同一时间段内处理多个任务的能力。并发并不意味着这些任务一定是在同一时刻同时执行,而是指在一个时间段内可以对多个任务进行调度和管理,使它们交替进行。

例子:

  • 在操作系统中,多个应用程序或多个线程可能在同一时刻运行,但实际上它们可能是交替执行的(例如,操作系统为每个任务分配时间片)。

同步

同步指的是在多个任务之间按照一定顺序进行协调,通常是指一个任务必须等待另一个任务完成后才能执行。具有某种先后次序来运行,比如A计算则B输出这就是同步。在同步模型中,一个任务的执行通常依赖于其他任务的结果。

注意:同步通常会使用阻塞操作,例如,wait()join()等,来确保任务按顺序执行。

例子:

  • 在传统的函数调用中,调用者必须等待被调用函数返回结果,才能继续执行后续代码。这是同步的行为。

异步

异步指的是任务执行时,不需要等待其他任务的完成,而是继续执行其他操作。异步就是彼此独立,当一个任务完成时,会通过某种机制(如回调、事件或消息)通知其他任务。

例子:

  • 在网络编程中,当客户端请求数据时,服务器可以异步处理该请求,继续处理其他客户端的请求,而不需要等待当前请求的处理结果。

互斥

互斥是一种用于确保同一时刻只有一个线程或进程能够访问共享资源的同步机制。比如A在执行这个过程,B就只能等待不能A执行一半B打断。它用于避免多个进程或线程并发访问共享资源时发生冲突或不一致。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

例子:

  • 在多线程环境下,多个线程共享一个全局变量时,使用互斥锁来确保在任意时刻只有一个线程能够修改该变量,从而避免数据竞争。

阻塞

阻塞是指在执行某个操作时,当前任务会被暂停,直到某个条件满足或者事件发生。阻塞操作会使当前线程停止执行,直到等待的条件或资源可用。阻塞通常发生在I/O操作、等待某个资源或同步原语(如信号量、互斥锁)时。关键点:

例子:

  • 例子程程序中,一个线程在调用 pthread_join() 等待另一个线程完成时会被阻塞,直到目标线程结束并返回结果。

非阻塞

非阻塞是指操作不会使当前任务暂停执行,而是立即返回。非阻塞操作不会等待某个条件或资源的可用,而是直接返回一个结果(通常是错误码或者状态值,表示操作未能完成)。

例子:

  • 在文件I/O操作中,使用非阻塞模式可以使程序在尝试读取文件时,如果数据还没准备好,就立即返回并执行其他任务,而不会阻塞当前线程。

注意:

  • 同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。
  • 阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。
  • 同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

4.你了解死锁吗?请你说说产生的条件?如何解决?

死锁是指在并发或多线程编程中,两个或多个进程或线程因相互等待而导致的一种僵局状态。

死锁的条件:(四个必要条件)

  • 互斥条件:至少有一个资源必须处于非共享模式,即每次只有一个进程可以使用该资源。其他进程必须等待。直到进程使用完后释放该资源。
  • 请求与保持条件:一个进程已经持有至少一个资源,并且又请求其他尚被其他进程持有的资源。
  • 不剥夺条件:进程持有的资源不能被强制剥夺,资源只能由持有者释放后,其他进程才能使用。
  • 循环等待条件:存在一个进程等待链,其中每个进程都在等待下一个进程持有的资源,且这个链是循环的。例如,进程A等待资源B,进程B等待资源C,进程C等待资源A。

例子:

假设有两个进程(P1 和 P2)和两个资源(R1 和 R2),它们之间的关系如下:

  1. P1首先获取资源R1,等待资源R2。
  2. P2首先获取资源R2,等待资源R1。

如果这两个进程之间相互等待对方释放资源,则会发生死锁:

  • P1 正在等待 P2 释放 R2。
  • P2 正在等待 P1 释放 R1。

由于两个进程都在等待对方释放资源,因此它们都无法继续执行,形成了死锁。

如何解决:

  • 设计资源分配策略,确保不会进入死锁状态。例如,资源按固定顺序申请或限制某些资源的并发访问。
  • 定期检查系统是否进入死锁状态,一旦检测到死锁,采取措施恢复,例如强制终止进程或回滚操作。
  • 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。

常见场景:

  • 数据库中的死锁:多个事务相互等待对方释放锁,从而形成死锁。数据库管理系统通常通过死锁检测来处理这种情况。
  • 文件系统中的死锁:多个进程可能同时请求对文件的锁,并相互等待对方释放锁,导致死锁。
  • 线程池中的死锁:在多线程编程中,如果线程池中的线程相互依赖并且互相等待对方完成任务,就会导致死锁。

5.什么是互斥锁机制?互斥锁和读写锁有什么区别?

互斥锁是一种用于实现线程或进程间互斥的同步机制,目的是避免多个线程或进程在同一时刻访问共享资源,从而导致数据竞争和资源不一致。它是一种排他性锁,当一个线程获得互斥锁时,其他线程无法访问被该锁保护的资源,直到持有锁的线程释放锁。当获取锁失败时,线程会进入睡眠,等待锁释放时被唤醒。

  • 互斥性:同一时刻只有一个线程能够持有锁,其他线程只能等待。
  • 阻塞行为:如果一个线程尝试获取一个已被其他线程持有的锁,它会被阻塞,直到锁被释放。
  • 排他性:互斥锁的设计是为了排除其他线程对共享资源的访问,避免发生数据竞争。

注意:互斥锁的常见问题

死锁

  • 如果多个线程互相等待对方释放锁,可能会导致死锁。为避免死锁,可以使用“锁顺序”策略或者避免锁嵌套等。

锁竞争

  • 当多个线程频繁争夺同一个锁时,会造成性能问题。为了减少锁竞争,可以尝试细化锁粒度或使用更高效的同步机制,如读写锁。

性能开销

  • 频繁的锁操作(尤其是在高并发环境中)会导致性能下降。减少锁的持有时间,并尽量降低锁的争用,可以提升性能。

锁粒度

  • 锁的粒度过大(例如锁住整个大范围的共享资源)会增加阻塞的机会,而锁粒度过小(例如锁住每个共享资源的部分)则可能导致管理复杂度增大。

互斥锁与读写锁的区别

锁的类型

独占锁(每次只有一个线程可以访问共享资源)

读锁共享,写锁独占(多个线程可以同时读取,但写入是独占的)

并发读

不支持并发读,读操作和写操作都需要锁

支持多个线程并发读取共享资源,但写操作是独占的

写操作

写操作必须独占锁(没有并发写操作)

写操作必须独占锁,但可以有多个读操作并行

性能

当有大量读取操作时,性能较差,因为写操作和读操作都需要锁

当读操作远多于写操作时,性能更高,因为读操作可以并行执行

适用场景

适用于读写操作较少、对共享资源的访问较为简单的情况

适用于读操作远多于写操作的场景

复杂度

较为简单,线程控制较为直接

较为复杂,涉及读锁和写锁的管理

资源竞争

所有操作都需要竞争锁,因此容易产生竞争

读锁并发,减少了读取操作的资源竞争,但写锁会产生较大竞争

6.什么是自旋锁,和互斥锁有什么区别?

当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁。

原理

  • 当线程尝试获取锁时,如果锁已经被其他线程持有,线程不会被阻塞,而是不断地在循环中检查锁的状态。
  • 如果锁没有被释放,线程会一直检查,并且不断地执行CPU指令来检查锁的状态(即自旋)。
  • 一旦锁被释放,线程会成功获得锁并进入临界区执行任务。
  • 执行完任务后,线程释放锁,其他线程则可以获取锁。

自旋锁的缺点

  1. CPU资源浪费:自旋锁的最大问题是“忙等待”,即当锁被其他线程占用时,线程不断地进行检查,占用CPU资源。如果锁持有时间较长,这种做法会浪费大量的CPU时间,影响系统的整体效率。
  2. 可能导致活锁:在某些情况下,如果多个线程竞争同一个自旋锁,可能会导致线程在不断的自旋等待中消耗资源,但由于竞争激烈,始终无法获得锁,这种情况称为“活锁”。
  3. 死锁风险:如果自旋锁的实现不当,仍然可能会遇到死锁问题,特别是在自旋锁的使用过程中,没有正确释放锁,或者有多个线程循环等待锁时。
  4. 不适用于单核CPU:由于自旋锁的“自旋”过程会消耗CPU资源,因此在单核CPU上使用自旋锁可能会导致性能下降,因为在单核的情况下,线程无法真正并行运行,自旋等待会浪费宝贵的CPU时间。

自旋锁与互斥锁的区别

锁的获取方式

线程自旋等待锁的释放(忙等待)

线程阻塞等待锁的释放

适用场景

锁持有时间短、竞争不激烈的场景

锁持有时间较长或线程阻塞等待较长的场景

性能

当锁竞争不激烈时可以提供较好的性能

适用于锁竞争激烈的场景,但有上下文切换开销

资源开销

在锁持有时间较长时浪费大量CPU资源

由于线程阻塞,避免了自旋造成的资源浪费

上下文切换

没有上下文切换开销

线程可能被挂起并发生上下文切换

#秋招##春招##面经##嵌入式##牛客激励计划#

嵌入式/C++八股 文章被收录于专栏

本人双飞本,校招上岸广和通。此专栏覆盖嵌入式常见面试题,有C/C++相关的知识,数据结构和算法也有嵌入式相关的知识,如操作系统、网络协议、硬件知识。本人也是校招过来的,大家底子甚至项目,可能都不错,但是在面试准备中常见八股可能准备不全。此专栏很适合新手学习基础也适合大佬备战复习,比较全面。最终希望各位友友们早日拿到心仪offer。也希望大家点点赞,收藏,送送小花。这是对我的肯定和鼓励。 持续更新

全部评论
加更加更
点赞 回复 分享
发布于 今天 13:01 陕西
可以
点赞 回复 分享
发布于 今天 13:41 陕西
已老实这么多要背嘛
点赞 回复 分享
发布于 今天 17:27 陕西

相关推荐

评论
2
4
分享

创作者周榜

更多
牛客网
牛客企业服务