【C++八股-第17期】操作系统-进程与线程③

❤此贴满十朵花花,速更下一贴

alt

提纲:

👉 八股:

  1. 协程的轻量级表现在何处?

  2. 线程间通信和同步的方式有哪些?

  3. 为什么要在进程之外额外引入线程?

  4. 编写在单核处理器上运行的多线程程序是否需要加锁?

  5. 简单介绍一下多进程和多线程的区别

  6. 简述互斥锁机制

  7. 互斥锁和读写锁有什么区别

  8. 进程为什么比线程慢?

  9. 介绍一下线程池设计思路,线程池中的线程数量由什么确定?

  10. 为什么创建线程池?

  11. 对比一下核心线程普通线程

1. 协程的轻量级表现在何处?

协程(coroutines)是一种轻量级的线程

协程的轻量级表现主要体现在以下几个方面:

  • ① 上下文切换开销小
    • 线程: 线程上下文切换需要保存和恢复寄存器、堆栈指针和程序计数器,还需要进行内核态和用户态之间的切换,开销较大。
    • 协程: 协程上下文切换只需要保存和恢复少量的寄存器信息,不涉及内核态的切换,因此开销非常小。
  • ② 内存占用少
    • 线程: 每个线程都有自己的栈空间(通常为几MB),同时线程还需要维护内核线程的控制块(TCB)。
    • 协程: 协程通常只需要几KB的栈空间,而且协程不需要内核维护控制块,因为它们是用户态的调度机制。
  • ③ 无需锁机制
    • 线程: 多个线程访问共享资源时需要使用锁机制(如互斥锁、读写锁等)来保证线程安全,锁的获取和释放增加了开销。
    • 协程: 协程在同一线程内调度,因此不需要锁机制来保护共享资源,避免了锁竞争和上下文切换带来的开销。
  • ④ 调度灵活
    • 线程: 线程的调度是由操作系统内核负责的,调度算法较为复杂,调度频繁会导致较大的系统开销。
    • 协程: 协程的调度通常由用户程序自己控制,可以根据需要灵活地切换,不会产生额外的系统调用开销。
  • ⑤ 创建和销毁开销小
    • 线程: 创建和销毁线程需要系统调用,涉及到分配和释放资源,开销较大。
    • 协程: 创建和销毁协程只是分配和释放少量的用户态内存,不涉及系统调用,开销非常小。

   

   

2. 线程间通信和同步的方式有哪些?

线程/进程间通信

通信(Inter-process/Inter-thread Communication, IPC/ITC)指的是在多个线程或进程之间传递数据。常见的通信方式有:

  • 消息队列: 线程/进程之间通过队列传递消息。
  • 管道: 通过管道(Pipe)进行数据传输。
  • 共享内存: 多个线程/进程共享同一块内存区域,直接读写数据。
  • 套接字: 通过网络套接字进行通信,适用于分布式系统。
  • 文件: 通过文件进行数据交换。
  • 信号量: 用于信号传递,但主要用于同步。

线程/进程间同步

同步(Synchronization)指的是在多个线程或进程之间协调操作顺序,以确保数据一致性和避免竞态条件。常见的同步方式有:

  • 互斥锁(Mutex): 确保同一时间只有一个线程可以访问共享资源。

  • 读写锁(Read-Write Lock): 允许多个线程同时读取,但写操作独占。

  • 条件变量(Condition Variable): 用于线程间的通知和等待机制。

  • 信号量(Semaphore): 控制资源的访问,既可以用于同步也可以用于通信。

  • 屏障(Barrier): 补充 使多个线程在某个同步点上等待,直到所有线程都到达。

进程与线程对比

  • 信息共享:

    • 进程: 进程间的信息共享比较困难。除了只读的代码段外,父子进程并不共享内存。因此,进程间通信(IPC)需要使用专门的机制,如管道、消息队列、共享内存等。
    • 线程: 同一进程内的多个线程共享进程的内存(代码段、数据段、堆和文件描述符),因此线程间信息共享非常方便。
  • 创建和销毁开销:

    • 进程: 创建进程的代价较高。即便利用写时复制(Copy-On-Write)技术,仍然需要复制进程属性(如内存页表、文件描述符表等)。因此,调用fork()来创建进程在时间上开销较大。
    • 线程: 创建线程的代价要低得多。线程共享进程的虚拟地址空间,无需采用写时复制来复制内存,也无需复制页表。通常,创建线程比创建进程快10倍甚至更多。
  • 资源占用:

    • 进程: 每个进程都有独立的内存空间和资源,这意味着进程占用的系统资源更多。
    • 线程: 线程共享进程的资源,因此同一进程内的线程占用的资源较少。
  • 执行效率:

    • 进程: 进程之间的上下文切换涉及更多的状态保存和恢复,开销较大。
    • 线程: 线程之间的上下文切换较快,因为线程共享进程的大部分资源。

拓展(了解即可):

区别与联系

  • 目的不同: 通信的目的是在多个线程或进程之间传递数据,而同步的目的是协调多个线程或进程的执行顺序以保护共享资源。

  • 实现机制: 通信主要依赖于消息队列、管道、共享内存等机制;同步主要依赖于互斥锁、读写锁、条件变量等机制。

  • 关联性: 有时同步机制也会涉及通信,例如信号量既可以用于同步,也可以用于通知线程/进程间的状态变化,从而实现通信。

   

   

3. 为什么要在进程之外额外引入线程?

  • 在早期的多任务操作系统中,进程是基本的执行单元。

  • 每次切换进程时,系统都需要保存和恢复进程的资源,这个过程叫做上下文切换。

  • 然而,频繁的进程切换会产生额外的开销,严重影响系统性能。

  • 为了减少这些开销,人们将多个任务放在同一个进程中,每个任务作为一个更小的执行单元来并发运行,这种更小的执行单元就是线程。

   

   

4. 编写在单核处理器上运行的多线程程序是否需要加锁?

单个核心:只有一个处理核心,意味着在任意时刻只能执行一个线程或进程。

需要加锁

单核机器中的多线程程序仍然存在线程同步问题,例如在抢占式操作系统中,操作系统会通过快速切换线程(时间片轮转)来模拟并发执行。

当运行多线程程序时,操作系统通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。

如果这两个线程共享某些数据,不使用线程锁的前提下,依旧可能会导致数据竞争和不一致问题。

   

   

5. 简单介绍一下多进程和多线程的区别

特点 多进程 多线程
从属关系 一个进程可以包含多个独立的子进程,但它们是独立的执行单元。 一个进程包含多个线程
稳定性 一个进程挂掉不影响其他进程 一个线程挂掉可能导致整个进程挂掉,因为线程共享进程的内存空间。
系统开销 进程切换开销大 线程切换开销小
内存使用 每个进程拥有独立的内存空间 线程共享进程的内存
切换开销 需要刷新TLB并获取新的地址空间,切换开销大 只需切换硬件上下文和内核栈,切换开销小
通信方式 通信方式多样,如管道、消息队列、共享内存、套接字等 通信方式简单,如共享内存、条件变量、互斥量等
适用场景 高稳定性和隔离性需求,如数据库服务器、分布式计算 需要频繁切换任务和共享大量数据,如图形界面、Web服务器

   

   

6. 简述互斥锁机制

互斥锁(Mutex)可以确保同一时间只有一个线程可以进入被保护的临界区域。

当一个线程持有互斥锁时,其他试图获取该锁的线程会被阻塞,直到持有锁的线程释放它。

   

   

7. 互斥锁和读写锁有什么区别

特性 互斥锁 读写锁
锁类型 不区分读和写,独占 区分读和写
操作影响 任何访问资源的操作都需要获取互斥锁 读操作可以并发,写操作是独占(同一时间内只允许一个写操作,但是允许多个进程/线程同时读对象)
并发性 低,同一时间只允许一个线程访问资源 高,多个读线程可以同时持有读锁
写操作阻塞读操作
读操作阻塞写操作 是(写操作优先级高于读操作时)
使用场景 对写操作频繁的场景 对读操作频繁且需要提高并发读取的场景

   

   

8. 进程为什么比线程慢?

重复问题,单拎出来加深记忆

  • 系统资源开销:

    • 内存消耗: 每个进程都有独立的地址空间,包括代码段、数据段、堆和栈,而线程共享进程的地址空间。因此,创建进程需要为每个进程分配独立的内存空间,而创建线程则需要更少的系统资源。
    • 资源管理: 操作系统需要管理每个进程的独立资源,如文件描述符、进程表等,这些管理和调度的开销比管理线程的开销更大。
  • 上下文切换开销:

    • TLB刷新: 进程切换时,需要刷新TLB(Translation Lookaside Buffer,地址转换高速缓存),因为不同的进程可能有不同的页表结构,这会增加内存访问的开销。
    • 地址空间切换: 进程切换需要切换到新的地址空间,而线程切换只需要切换到新的栈和寄存器组,不涉及地址空间的改变,因此线程切换的开销更小。
  • 通信与同步开销:

    • 进程间通信: 通常需要使用额外的通信机制,如管道、消息队列、共享内存等,这些机制需要额外的内核操作和内存分配,因此通信的开销比较大。
    • 线程间通信: 线程共享进程的内存空间,可以直接访问共享的数据结构,通信更加简单和高效,同步开销也较小。

   

   

9. 介绍一下线程池设计思路,线程池中的线程数量由什么确定?

alt

线程池的设计思路和生产者、消费者思路差不多

  • 1、建立一个生产者任务容器,用来存储要被执行的任务
  • 2、启动几个消费者线程,消费者线程是用来处理生产者任务容器中的任务的
  • 3、有任务进入,消费者被唤醒处理,处理完了回头找新的任务。

   

线程池数量确定依据:

  • CPU密集型 应用,则线程池大小设置为:CPU数目+1 (线程池最佳线程数目的估值)

  • IO密集型 应用,则线程池大小设置为:2*CPU数目+1 (线程池最佳线程数目的估值)

   

  • 假设以100%的CPU利用率来说,要达到100%的CPU利用率,对于一个CPU就要设置其利用率的倒数个数的线程数

   

  • 线程池最佳线程数目 = ( (线程等待时间(IO)+线程CPU时间(计算)) /线程CPU时间 )* CPU数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

  • 线程池所有任务的CPU操作耗时和IO操作耗时求个平均值即可。

   

   

10. 为什么创建线程池?

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。同时线程池也是为了提升系统效率

   

   

11. 对比一下核心线程普通线程

核心线程:

  • 任务优先级高: 核心线程通常负责优先级较高或者紧急的任务处理。
  • 固定数量: 核心线程数目通常是固定的,它们长时间存在于系统中,准备接收和处理任务。
  • 任务处理效率: 由于长时间存在且不会轻易销毁,核心线程通常能够快速响应任务,减少任务处理的启动和销毁开销。

普通线程:

  • 任务队列处理: 普通线程通常在任务队列中等待任务的到来,只有在有任务需要处理时才会被创建和启动。
  • 临时性: 普通线程的生命周期较短,任务结束后可能会被销毁,以节省系统资源。
  • 任务处理能力: 普通线程的创建和销毁开销相对较大,但能够动态地处理任务队列中的任务。

拓展(了解即可):

在系统中,核心线程负责优先处理任务。如果任务队列已满且核心线程仍在处理任务,系统会根据需要再创建普通线程来处理任务,以确保任务能够及时被处理。

这种策略可以保证系统在高负载情况下仍能有效处理任务,确保系统的响应速度和性能。

   

   

全部评论
以送
点赞 回复 分享
发布于 07-18 19:27 河南

相关推荐

1. 讲讲虚拟内存2. 讲讲你去取一个文件的过程,操作系统层面中间执行过的所有系统内存,磁盘?3. 线程哪些空间共享,哪些空间不共享呢,线程更轻量级,轻量级在哪里呢,线程创建和进程创建在操作系统层面有什么区别,讲讲这个过程4. Mysql, redis, mongoDb, ES里面的索引分别是怎样存储的    mongoDB不知道,其他三个说了说(我乱猜了一个mongDB的,说是存图片视频的,被骂了,让我不懂就不要说)5. ES的索引表是按照什么排序的呢?6. 如果不考虑互联网,有哪些排序方式呢7. 计算机网络你了解吗ssL加密算法你了解吗8. 对称加密和非对称加密有什么区别9. 数字签名和数字证书是什么,讲一讲10. Kafka的消息失败有哪些情况,怎么保证消息的一致性11. 算法题: 数字1-n,string形式的字典序排序第k个数字如1-1011,排序是1, 10, 100, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 101, 1010, 1011,。。。lc.386算法题没写出来,去看力扣,居然是中等,woc,我以为hard让他给我换一道,他表示不愿意,就这道,emmmmmm问面评:回去多看看基础吧,我们比较在意基础,就这样感受:从基础到算法题被从头到尾搏杀,基础题问的我心梗,已经开始胡言乱语了,算法题半个小时没写出来
查看12道真题和解析
点赞 评论 收藏
分享
18 11 评论
分享
牛客网
牛客企业服务