【C++八股-第17期】操作系统-进程与线程③
❤此贴满十朵花花,速更下一贴
提纲:
👉 八股:
协程的轻量级表现在何处?
线程间通信和同步的方式有哪些?
为什么要在进程之外额外引入线程?
编写在单核处理器上运行的多线程程序是否需要加锁?
简单介绍一下多进程和多线程的区别
简述互斥锁机制
互斥锁和读写锁有什么区别
进程为什么比线程慢?
介绍一下线程池设计思路,线程池中的线程数量由什么确定?
为什么创建线程池?
对比一下
核心线程
和普通线程
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. 介绍一下线程池设计思路,线程池中的线程数量由什么确定?
线程池的设计思路和生产者、消费者思路差不多
- 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. 对比一下核心线程
和普通线程
核心线程:
- 任务优先级高: 核心线程通常负责优先级较高或者紧急的任务处理。
- 固定数量: 核心线程数目通常是固定的,它们长时间存在于系统中,准备接收和处理任务。
- 任务处理效率: 由于长时间存在且不会轻易销毁,核心线程通常能够快速响应任务,减少任务处理的启动和销毁开销。
普通线程:
- 任务队列处理: 普通线程通常在任务队列中等待任务的到来,只有在有任务需要处理时才会被创建和启动。
- 临时性: 普通线程的生命周期较短,任务结束后可能会被销毁,以节省系统资源。
- 任务处理能力: 普通线程的创建和销毁开销相对较大,但能够动态地处理任务队列中的任务。
拓展(了解即可):
在系统中,核心线程负责优先处理任务。如果任务队列已满且核心线程仍在处理任务,系统会根据需要再创建普通线程来处理任务,以确保任务能够及时被处理。
这种策略可以保证系统在高负载情况下仍能有效处理任务,确保系统的响应速度和性能。