1. 进程通信中的管道实现原理
管道是半双工的进程间通信方式,核心基于内核缓冲区实现:
- 系统在内核中开辟一块临时的缓冲区,作为管道的载体;
- 管道以文件描述符的形式暴露给进程,进程通过读/写文件描述符,完成对内核缓冲区的读写操作;
- 数据按先进先出原则传输,写进程向缓冲区写入数据,读进程从缓冲区读取数据,内核自动维护读写位置;
- 分为匿名管道和命名管道:匿名管道基于pipe系统调用创建,仅能用于父子进程间通信;命名管道基于mkfifo创建,以文件形式存在于文件系统,可用于无亲缘关系的进程间通信。
2. 简述mmap的原理和使用场景
原理
mmap是内存映射文件机制,核心是将磁盘文件的部分或全部,直接映射到进程的虚拟地址空间:
- 进程调用mmap时,内核在进程虚拟地址空间中分配一段连续的虚拟内存区域;
- 内核建立该虚拟区域与磁盘文件的映射关系,但不立即加载文件数据到物理内存;
- 进程首次访问该虚拟内存时,触发缺页中断,内核才将对应文件数据加载到物理内存,并更新页表;
- 进程对映射区域的读写,会被内核同步到磁盘文件(或通过msync主动同步),实现内存与文件的双向绑定。
使用场景
- 大文件高效读写:避免频繁read/write的用户态与内核态切换,减少数据拷贝;
- 进程间通信:多个进程映射同一文件,实现共享内存式通信;
- 高性能网络IO:结合sendfile实现零拷贝,提升数据传输效率。
3. 互斥量能不能在进程中使用?
可以,但需满足共享内存条件,分为两种情况:
- 线程级互斥量:默认基于进程内的局部内存创建,仅能用于同一进程内的线程同步,无法跨进程使用;
- 进程级互斥量:通过将互斥量创建在共享内存区(如mmap映射区、shmget共享内存),并设置进程共享属性(如POSIX互斥量的PTHREAD_PROCESS_SHARED属性),即可实现跨进程同步。
4. 协程是轻量级线程,轻量级表现在哪里?
协程的“轻量级”核心体现在调度和资源开销上,与操作系统线程对比差异显著:
- 调度开销:协程由用户态调度器管理,而非操作系统内核,切换时无需陷入内核、无需保存/恢复CPU寄存器、无需更新页表,切换耗时仅为线程的1/100甚至更低;
- 内存开销:线程默认栈大小为几MB,而协程栈为几KB到几十KB(可动态扩容),单个进程可创建数万个协程,远多于线程的创建上限;
- 资源依赖:协程基于线程实现(N:M模型),多个协程复用一个线程的内核资源,减少内核态资源的占用。
5. 常见信号有哪些,表示什么含义?
信号是操作系统向进程发送的异步通知,核心常见信号及含义如下:
- SIGINT (2):中断信号,由Ctrl+C触发,通知进程终止运行;
- SIGTERM (15):终止信号,kill命令默认发送,允许进程捕获并执行清理逻辑后退出;
- SIGKILL (9):强制终止信号,无法被捕获、阻塞或忽略,用于强制杀死进程;
- SIGSEGV (11):段错误信号,进程访问非法内存地址时触发;
- SIGPIPE (13):管道破裂信号,向读端已关闭的管道写入数据时触发;
- SIGCHLD (17):子进程状态变化信号,子进程退出或暂停时,内核向父进程发送该信号。
6. 线程同步方式有哪些?
线程同步的核心是解决临界区资源竞争问题,常见方式分为基础机制和高级机制:
基础同步机制
- 互斥锁:保证同一时间只有一个线程能进入临界区,适用于排他性资源访问;
- 读写锁:区分读操作和写操作,读共享、写排他,适合读多写少的场景;
- 信号量:分为二进制信号量(等价于互斥锁)和计数信号量,可实现多线程的资源计数与同步;
- 条件变量:配合互斥锁使用,实现线程间的“等待-唤醒”机制,解决线程间的依赖问题。
高级同步机制
- 屏障:等待所有线程到达指定点后,再继续执行,适用于批量任务协同;
- 自旋锁:线程获取锁失败时不阻塞,而是循环尝试,适合临界区执行时间极短的场景。
7. 什么是死锁,产生的条件,如何解决?
死锁定义
多个进程/线程因竞争资源,互相持有对方所需的资源,且均不释放已持资源,导致所有进程/线程陷入永久阻塞的状态。
产生的四个必要条件(缺一不可)
- 互斥条件:资源为排他性资源,同一时间仅能被一个进程/线程占用;
- 请求与保持条件:进程/线程持有部分资源时,又请求其他进程/线程持有的资源;
- 不可剥夺条件:进程/线程已持有的资源,无法被其他进程/线程强制剥夺,只能主动释放;
- 循环等待条件:多个进程/线程形成资源请求的闭环,即A等B的资源,B等C的资源,C等A的资源。
解决方法
- 预防死锁:破坏死锁的任一必要条件(如按序申请资源,破坏循环等待;资源预分配,破坏请求与保持);
- 避免死锁:通过银行家算法等,动态判断资源分配是否会导致死锁,仅在安全时分配资源;
- 检测与解除:定时检测系统是否存在死锁,若存在则通过剥夺资源、杀死进程等方式解除。
8. 有了进程,为什么还要有线程?
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位,引入线程的核心目的是提升并发效率、降低开销:
- 创建与切换开销:进程创建时需分配独立的地址空间、页表、文件描述符等资源,切换时需刷新MMU、切换地址空间,开销极大;线程共享进程的地址空间和资源,创建仅需分配栈和寄存器,切换仅需保存线程上下文,开销远低于进程;
- 通信效率:进程间通信需借助内核(管道、共享内存等),存在用户态与内核态切换;线程共享进程的堆内存和全局变量,可直接通信,效率极高;
- 并发粒度:进程的并发粒度较粗,一个进程阻塞(如IO等待)会导致整个进程无法执行;线程阻塞时,同一进程的其他线程可继续占用CPU执行,提升了CPU利用率。
9. 单核机器上写多线程程序,是否要考虑加锁,为什么?
需要加锁,核心原因与CPU调度的时间片轮转机制和指令重排序有关:
- 时间片轮转:单核CPU通过时间片轮转调度线程,线程执行到临界区中间时,可能被操作系统挂起,切换到其他线程执行;若临界区资源未加锁,后续线程会读取到不完整的资源数据,导致数据竞争;
- 指令重排序:编译器和CPU为优化性能,会对指令进行重排序,多线程环境下,未加锁的临界区操作可能出现执行顺序错乱,破坏数据一致性;
- 示例:两个线程同时对全局变量i执行i++(非原子操作,分为读、加、写三步),若不加锁,可能出现两个线程同时读取i=0,最终i仅变为1,而非预期的2。
10. 多线程和多进程的不同?
11. 简述互斥锁的机制,互斥锁与读写锁的区别?
互斥锁机制
互斥锁基于原子操作和阻塞唤醒实现,核心逻辑:
- 互斥锁包含一个状态位(锁定/未锁定),通过原子指令(如test_and_set)实现锁的获取;
- 线程请求锁时,若锁为未锁定状态,原子性将其设为锁定,线程进入临界区;
- 若锁为锁定状态,请求线程会被阻塞,放入等待队列,直到持有锁的线程释放锁;
- 持有锁的线程执行完临界区代码后,释放锁并唤醒等待队列中的线程。
互斥锁与读写锁的区别
12. 什么是信号量,有什么作用?
信号量定义
信号量是一种计数器,基于PV操作(wait/signal)实现进程/线程间的同步与互斥,由操作系统保证PV操作的原子性。
核心作用
- 实现互斥:使用二进制信号量(值为0或1),等价于互斥锁,实现临界区的排他性访问;
- 实现同步:使用计数信号量(值为非负整数),通过计数控制资源的可用数量,实现线程间的依赖等待(如生产者-消费者模型中,控制缓冲区的空/满数量);
- 资源计数:限制同时访问某资源的进程/线程数量(如连接池的最大连接数限制)。
13. 进程、线程的中断切换过程是怎样的?
进程/线程的切换本质是上下文的保存与恢复,触发于中断(外部中断、异常中断、系统调用),核心步骤分为进程切换和线程切换,二者核心差异在于是否切换地址空间:
通用切换流程
- 中断触发:CPU执行指令时检测到中断,暂停当前进程/线程的执行;
- 保存上下文:将当前进程/线程的CPU寄存器值(程序计数器、栈指针、通用寄存器等)保存到内核栈中;
- 中断处理:内核执行中断服务程序,判断是否需要进行进程/线程调度;
- 选择下一个任务:调度器根据调度算法(如RR、CFS),从就绪队列中选择下一个要执行的进程/线程;
- 恢复上下文:从下一个进程/线程的内核栈中,恢复CPU寄存器值;
- 返回执行:CPU跳转到新进程/线程的指令地址,继续执行。
进程切换 vs 线程切换
- 进程切换:额外增加地址空间切换步骤,内核需刷新MMU、切换页表、清空TLB,开销极大;
- 线程切换:线程共享进程地址空间,无需切换页表和MMU,仅需保存/恢复线程专属的上下文,开销极低。
14. 简述自旋锁和互斥锁的使用场景
自旋锁核心特性
线程获取锁失败时,不阻塞、不放弃CPU,而是通过循环轮询的方式尝试获取锁,直到成功。
互斥锁核心特性
线程获取锁失败时,会被阻塞,放弃CPU,进入等待队列,直到被唤醒。
使用场景划分
- 自旋锁适用场景:临界区执行时间极短(微秒级),轮询的开销远小于线程阻塞唤醒的开销;多核CPU环境,避免单核下自旋导致的CPU资源浪费;内核态编程(如中断处理程序),无法进行阻塞操作的场景。
- 互斥锁适用场景:临界区执行时间较长(毫秒级及以上),自旋
全部评论
(1) 回帖