首页 > 大厂系列:操作系统八股文,速速收藏(八)
头像
后端offer收割
发布于 今天 09:38
+ 关注

大厂系列:操作系统八股文,速速收藏(八)

61.信号量和互斥锁应用场景有什么区别?

互斥锁:适用于保护临界区,只允许一个线程访问资源的场景,如全局变量、共享内存、文件等需要严格互斥访问的场景。

信号量:适用于资源数量有限且需要控制并发数的场景,如数据库连接池、线程池、对多个资源的并发访问控制等。

62.线程间同步方式有哪一些?

互斥锁(Mutex)

  • 功能:互斥锁是最常见的同步机制,它通过锁定资源来保证同一时刻只有一个线程能够访问临界区。互斥锁可以防止多个线程同时对共享资源进行修改,避免数据竞态。
  • 实现:一个线程请求锁,如果锁已被其他线程占用,那么请求线程将被阻塞,直到锁被释放。
  • 适用场景:适用于需要保护临界区,确保多个线程不同时访问共享资源的场景,如文件操作、共享内存的访问等。
  • 缺点:如果使用不当,可能会导致死锁(deadlock)或线程饥饿(starvation)。

读写锁(Read-Write Lock)

  • 功能:读写锁是一种特殊的锁,允许多个线程同时读取资源,但只允许一个线程写资源。它通常由两个锁组成:一个是读锁,另一个是写锁。
  • 实现:当有线程持有写锁时,其他线程无法访问资源;当有线程持有读锁时,其他线程可以继续读取,但不能修改资源。
  • 适用场景:适用于读操作远多于写操作的场景。例如,数据库查询和缓存系统中,读操作频繁,写操作少时,使用读写锁可以显著提高性能。
  • 缺点:写操作的性能较差,因为写锁是独占的,其他读写操作都需要等待。

信号量(Semaphore)

  • 功能:信号量是一种同步机制,用于控制多个线程对共享资源的访问。信号量由一个计数器来表示资源的数量。线程可以请求(减少)信号量,成功则继续执行;如果信号量的值为零,线程将被阻塞,直到有线程释放资源并增加信号量值。
  • 实现:信号量可以是二值信号量(二值锁)或计数信号量。二值信号量类似于互斥锁,只允许一个线程进入临界区;计数信号量则允许多个线程并发访问资源,直到计数器值为零。
  • 适用场景:适用于资源池的管理,如数据库连接池、线程池等。它也常用于限制多个线程并发访问某些资源的数量。
  • 缺点:信号量的管理比互斥锁复杂,且在使用时容易出现死锁或资源浪费。

条件变量(Condition Variable)

  • 功能:条件变量用于线程间的通信,允许线程在某个条件成立时才继续执行。线程可以等待某个条件(如共享资源的可用性),并在条件满足时被唤醒。通常与互斥锁配合使用,以确保操作的原子性。
  • 实现:条件变量通过 wait() 和 notify() 或 notify_all() 操作实现。wait() 操作使线程进入阻塞状态,直到条件满足;notify() 或 notify_all() 用于唤醒一个或所有等待线程。
  • 适用场景:适用于生产者-消费者问题、线程协调或需要在某些条件满足时才能继续执行的场景。例如,当一个线程需要等待其他线程完成某些任务后才能继续工作时,可以使用条件变量。
  • 缺点:需要与互斥锁一起使用,避免数据不一致的情况。

自旋锁(Spinlock)

  • 功能:自旋锁是一种轻量级的锁,线程在请求锁时,如果锁被占用,它会不断地自旋(循环检查锁的状态),直到锁被释放。自旋锁避免了线程被阻塞,减少了上下文切换的开销,但会导致 CPU 时间的浪费。
  • 实现:自旋锁通常是基于原子操作实现的,线程通过原子地检查锁的状态并等待它变为可用。
  • 适用场景:适用于锁持有时间非常短的场景,如短时间的临界区。自旋锁通常用于高性能要求的多核系统中,因为它避免了线程上下文切换的开销。
  • 缺点:如果锁的持有时间较长,可能导致 CPU 资源浪费。

屏障(Barrier)

  • 功能:屏障用于在多个线程之间同步。当线程到达屏障时,它们将等待其他线程到达屏障点,只有所有线程都到达屏障后,才能继续执行。屏障确保多个线程在某个特定点上同步。
  • 实现:屏障通常使用一个计数器来记录到达的线程数量,直到计数器值达到指定数量,所有线程才会被释放。
  • 适用场景:适用于在某些阶段需要同步多个线程的场景,例如并行计算中,多个线程需要在某个步骤后同步再继续执行后续步骤。

事件(Event)

  • 功能:事件通常用于线程间的通知机制,允许一个线程通知其他线程某个事件的发生。线程可以在等待事件的状态下进入阻塞,当事件被触发时,相关线程被唤醒。
  • 实现:事件通常与条件变量结合使用,用于线程同步和通信。
  • 适用场景:适用于线程间通知和等待机制,如多线程任务的协作和分发。

读写锁(Read-Write Lock)

  • 功能:读写锁是一种特殊的同步机制,它允许多个线程同时读取共享资源,但当一个线程写共享资源时,其他线程的读写操作都被阻塞。
  • 适用场景:适用于读操作远多于写操作的场景,典型应用场景包括数据库、缓存系统等。

Atomic 操作

  • 功能:原子操作是指不可分割的操作,保证在执行过程中不会被中断。原子操作是线程同步的一种低级手段,通常由硬件支持,能够在不加锁的情况下确保操作的原子性。
  • 实现:原子操作通过硬件提供的指令(如 CAS)实现,通常用于简单的数据操作。
  • 适用场景:适用于一些简单的线程同步操作,如计数器的更新、标志位的检查等。

63.进程的调度算法有哪些?

先来先服务(FCFS, First-Come, First-Served):该算法按进程到达的顺序依次调度执行,首先到达的进程首先被执行。这是最简单的一种调度算法,容易实现,但会产生较长的平均等待时间,尤其是当一个长作业前面有多个短作业时(称为"饥饿"现象)。优点:简单易实现;缺点:可能导致较长的平均等待时间。

最短作业优先(SJF, Shortest Job First):该算法总是选择执行估计运行时间最短的进程。SJF 算法能够最小化平均等待时间,但前提是必须知道进程的执行时间。这在实践中很难做到,因为通常无法准确预测进程的运行时间。优点:能有效降低平均等待时间;缺点:难以实现(需要预测进程的执行时间);可能导致长作业“饥饿”。

优先级调度(Priority Scheduling):该算法根据每个进程的优先级进行调度,优先级高的进程先执行。如果有多个进程优先级相同,则使用其他调度算法(如 FCFS)来进一步决策。优先级可以是固定的(静态优先级)或动态的。优点:能够根据任务的重要性进行调度;缺点:可能导致低优先级的进程“饥饿”,特别是在使用静态优先级时。

时间片轮转(RR, Round Robin):时间片轮转算法是最常见的多任务调度算法,它为每个进程分配一个固定长度的时间片,进程在时间片内执行,当时间片用完后,如果进程未完成,就会被挂起并移到队列末尾,等待下一轮调度。优点:公平,所有进程都有相同的机会占用 CPU;适用于交互式系统;缺点:可能导致某些进程长时间不能完成(尤其是时间片设置过小时)。

多级队列调度(Multilevel Queue Scheduling):该算法将进程分配到不同的队列中,每个队列使用不同的调度策略。例如,可以将交互式进程放入高优先级队列,并使用时间片轮转调度,而将批处理进程放入低优先级队列,使用 FCFS 调度。优点:能够根据不同类型的进程采用不同的调度策略,优化性能;缺点:复杂性较高,需要对进程进行分类和调度策略的精细调整。

多级反馈队列调度(Multilevel Feedback Queue Scheduling):该算法是多级队列调度的扩展,它允许进程根据其执行时间和行为在不同的队列之间移动。例如,短作业可能会一直待在高优先级队列,而长作业可能会降级到低优先级队列。优点:灵活,能够动态调整进程的优先级;缺点:需要对进程的行为进行准确分析和管理。

最短剩余时间优先(SRTF, Shortest Remaining Time First):这是 SJF 的抢占式版本。在 SRTF 中,进程在运行时会定期检查剩余时间,如果有一个新进程的估计剩余时间更短,就会发生抢占。优点:能够更好地优化平均等待时间;缺点:需要预测剩余时间,可能导致高开销。

公平共享调度(Fair Share Scheduling):公平共享调度算法在多用户系统中非常有用,它通过确保每个用户或进程组能得到公平的资源份额来实现公平性。每个用户的进程会根据其用户配额或配给的资源量来调度。优点:能够确保多个用户或进程组得到公平的资源分配;缺点:实现复杂,调度决策可能会根据用户或进程的配额而有所偏差。

64.信号和信号量的区别

信号主要是用于进程间的事件通知,通常是由操作系统或其他进程发起,来通知目标进程某些事件发生,如中断、终止或暂停。信号是异步的,不涉及进程间的同步。

信号量则是一种进程同步工具,用于控制进程对共享资源的访问,避免并发访问造成资源冲突。信号量操作通常是同步的,且可以阻塞进程,直到资源可用。

65.有名管道和匿名管道的区别?

命名:有名管道有一个文件系统中的路径,可以通过该路径访问;匿名管道没有路径,仅通过文件描述符进行访问。

访问范围:有名管道可以跨会话、跨用户进程通信;匿名管道通常仅限于父子进程或同一会话中的进程。

生命周期:有名管道需要手动删除;匿名管道随进程的结束而自动清理。

通信方式:有名管道通常支持双向通信,匿名管道通常是单向的。

实现复杂度:有名管道比匿名管道实现复杂一些,需要文件系统支持和权限管理。

66.哪个进程间通信效率最高的?

共享内存通常是效率最高的进程间通信方式,特别适合需要频繁和大规模数据交换的应用,但需要进程间同步机制来防止数据竞争。

管道效率较高,适合父子进程或同一会话中的数据传输。

消息队列相对较慢,但适合需要有序消息传递的复杂应用。

信号适合传递简单的事件通知,不适合数据传输。

套接字适用于跨网络的进程间通信,但效率相对较低。

67.进程间有哪些通信方式?

管道(Pipes)

  • 匿名管道:主要用于具有亲缘关系的进程之间(如父子进程)。它是单向的,即数据只能从一个进程流向另一个进程。匿名管道通过内核缓冲区传输数据。通常用于简单、低开销的数据传递。
  • 有名管道(FIFO):与匿名管道类似,但可以用于没有亲缘关系的进程。它通过文件系统中的特殊文件(FIFO文件)进行通信。可以在任意两个进程之间传递数据,适用于同一台机器上的进程。

优点:简单、低延迟,适合流式数据传输。 缺点:只能用于单向数据流,且通常只能用于亲缘关系的进程。

消息队列(Message Queues): 消息队列允许进程将消息发送到一个队列中,并可以异步地从队列中读取消息。每条消息都具有一个优先级,进程可以根据优先级进行处理。消息队列可以跨进程、跨线程进行通信,通常适用于异步消息传递场景。

优点:支持多进程之间的异步通信,可以根据消息优先级处理消息。 缺点:相较于共享内存,效率较低,尤其在高频数据交换的情况下。

共享内存(Shared Memory): 共享内存是最为高效的进程间通信方式。多个进程可以访问同一块内存区域,数据交换几乎不需要拷贝,提供了非常高的性能。共享内存通常与同步机制(如信号量或互斥锁)结合使用,以避免数据竞争。

优点:高效、低延迟,适用于大规模数据交换。 缺点:需要进程间同步机制,管理相对复杂,可能会出现竞态条件。

信号(Signals): 信号是一种用于进程间通知的机制。它通常用于传递简单的控制信息或事件通知。例如,进程通过发送 SIGKILL 信号来杀死另一个进程,或者通过 SIGALRM 信号触发定时事件。

优点:适用于发送简单的事件通知或信号。 缺点:仅限于传递简单信息,不适合大规模数据传输,且不提供可靠性保证。

套接字(Sockets): 套接字是一种广泛用于网络通信的IPC方式。进程通过套接字发送和接收数据,它可以用于同一机器上的进程之间,也可以跨机器进行通信。套接字提供了非常高的灵活性,支持多种协议(如 TCP、UDP)。

优点:适用于跨主机、跨网络的进程间通信,支持双向通信。 缺点:效率相对较低,尤其是跨网络通信,存在协议栈的开销。

内存映射文件(Memory-mapped Files): 内存映射文件是一种将文件内容映射到进程的虚拟地址空间的方式。多个进程可以通过映射相同的文件来进行通信。它类似于共享内存,但它将文件内容与内存空间关联,进程通过访问内存中的映射区域来读写数据。

优点:可以将文件内容共享给多个进程,适合处理大型数据。 缺点:文件系统开销较大,可能会涉及磁盘I/O。

条件变量和信号量(Condition Variables and Semaphores): 条件变量和信号量用于进程同步,尤其是当多个进程需要协调访问共享资源时。这两种机制通常与其他IPC方法(如共享内存)结合使用。

优点:提供高效的同步机制,适用于需要高并发控制的场景。 缺点:仅适用于同步,不用于数据交换。

Remote Procedure Call (RPC): RPC允许不同计算机上的进程之间像调用本地函数一样调用远程进程的函数。RPC隐藏了网络通信的复杂性,提供了简单的接口,使得跨进程和跨机器通信变得更容易。

优点:简化了远程通信的复杂性,支持跨机器的进程间通信。 缺点:涉及较多的开销,可能受到网络延迟和带宽限制的影响。

68.为什么协程切换的开销比线程切换小?

协程切换发生在用户空间,不涉及操作系统的内核调度;

协程与线程共享同一个线程的资源,避免了复杂的内核栈切换;

协程的上下文保存和恢复只需要较少的资源,相比线程来说更加轻量级;

协程通常是非阻塞的,不涉及线程间的竞争和同步问题,减少了复杂的调度开销。

69.协程切换的本质是什么?

保存当前协程的状态

  • 每个协程都有自己的一组执行状态,这些状态通常包括指向当前执行位置的指针、局部变量、栈信息等。协程切换时,需要将当前协程的状态保存到某个地方(通常是栈或一个数据结构),以便在以后重新恢复。
  • 在一个协程内部,执行的位置通常通过一个指针来表示,比如指向下一条将要执行的指令。当协程切换时,这个指针需要保存,以便下次切换回来时可以从同一位置继续执行。

切换到另一个协程

  • 切换的核心是让当前线程的控制权从一个协程转移到另一个协程。这个过程通常由一个调度器(如协程库、事件循环等)来控制,它负责决定何时切换协程以及下一个将要执行的协程。
  • 当一个协程切换时,控制流会跳到另一个协程的入口点,执行另一个协程的代码,直到它再被挂起或切换。

恢复目标协程的状态

  • 当切换到目标协程时,需要恢复目标协程的执行状态。恢复的过程主要是恢复目标协程的执行位置,意味着指针将指向该协程的下一条指令,然后继续执行。
  • 恢复过程相对简单,只需要将之前保存的状态(如局部变量、指针等)重新加载到内存中,通常这些数据结构相对较小,因此恢复的开销也很小。

协程切换的本质是通过保存和恢复协程的状态来实现并发执行,它是轻量级的,不涉及操作系统的内核调度,因此切换的开销远低于线程切换。协程切换使得在单个线程中实现高效的并发执行成为可能。

70.协程和线程有什么区别?

执行模型

  • 线程:线程是操作系统管理的基本执行单元。每个线程有自己独立的堆栈和程序计数器(PC),可以被操作系统调度和切换。线程的创建和调度通常由操作系统的内核管理,因此线程之间的切换涉及到上下文切换(保存和恢复线程的状态)。
  • 协程:协程是用户级别的轻量级执行单元,它是在单一线程内通过用户空间的调度来实现并发的。协程的切换不涉及操作系统的内核调度,而是由程序员或协程库控制。协程之间的切换仅涉及保存和恢复协程的执行状态(如程序计数器、局部变量等),通常不需要操作系统干预。

资源消耗

  • 线程:创建线程需要分配独立的内存空间(如栈空间),并且线程之间的上下文切换需要保存和恢复较多的上下文信息(如寄存器、堆栈等),因此线程的创建和切换通常比较消耗资源。
  • 协程:协程非常轻量级,因为它们只需要保存少量的状态信息(如局部变量、栈指针等)。协程的创建和切换通常不需要操作系统内核的参与,因此它们的开销较小。

调度方式

  • 线程:线程的调度由操作系统的内核控制,通常是抢占式调度或基于时间片的调度。操作系统负责选择哪个线程运行,并且在需要时进行线程的切换。
  • 协程:协程的调度是协作式的,通常由用户或者协程库管理。当协程主动让出控制权(通过yield、await、sleep等操作),调度器会选择下一个需要执行的协程。协程之间的切换是显式的,程序员控制何时切换,而不像线程那样由操作系统自动切换。

上下文切换

  • 线程:线程切换需要操作系统介入,保存当前线程的上下文(如寄存器、栈等),并加载下一个线程的上下文。这个过程比较重,且涉及系统调用和内核态的操作,因此线程切换的开销较大。
  • 协程:协程切换只涉及保存和恢复少量的状态信息,通常是在用户态内完成,不涉及系统调用或内核态的切换。因此,协程切换的开销非常小,速度快。

并发性

  • 线程:线程的并发性依赖于操作系统的调度以及系统硬件的支持(如多核CPU)。多个线程可以并行执行,尤其是在多核处理器上,多个线程可以在不同的核心上同时运行,利用多核的计算能力。
  • 协程:协程通常在单核处理器上实现并发执行,所有的协程共享同一个线程。协程的并发是通过时间片轮转的方式来模拟的,尽管它们是并发执行的,但本质上是在单线程中交替执行。

适用场景

  • 线程:适用于需要真正并行执行的场景,特别是当程序可以并行处理多个独立任务时。例如,IO密集型操作、需要高并发处理的网络应用等。
  • 协程:适用于高并发、IO密集型场景,尤其是当任务之间需要频繁切换、并且每个任务的执行时间较短时。协程能够有效减少线程切换的开销,适合处理大量短时间的任务(如爬虫、Web请求处理等)。

同步机制

  • 线程:线程之间需要使用同步机制(如互斥锁、信号量、条件变量等)来保证线程间的数据一致性和避免竞争条件。线程同步可能会带来性能瓶颈和死锁问题。
  • 协程:协程通常通过事件循环、消息队列或异步编程来实现同步机制。由于协程在同一个线程内执行,它们共享相同的内存空间,通常不需要像线程那样的复杂同步机制。

71.悲观锁和乐观锁有什么区别?

悲观锁(Pessimistic Locking):假设并发场景中会频繁发生冲突,因此每次操作都认为会发生冲突。为了保证数据的一致性,悲观锁会在访问资源之前加锁,确保操作期间数据不会被其他线程或进程修改。悲观锁的主要思想是“我不信任其他线程,会采取措施避免冲突”。

乐观锁(Optimistic Locking):假设并发场景中冲突发生的概率较低,因此在操作时并不立即加锁,而是允许其他线程访问数据。在执行操作时,乐观锁会先读取数据,进行操作,最后检查数据是否在期间发生了变化,如果数据没有变化,则提交更新;如果发生了变化,操作会失败,需要重新尝试。乐观锁的主要思想是“我相信其他线程不会修改数据,所以我不需要加锁”。#牛客AI配图神器#

全部评论

(0) 回帖
加载中...
话题 回帖