进程,线程,协程

进程、线程、协程(面试完整版 + 高频考点)

一、基础概念

1. 进程(Process)

操作系统资源分配的最小单位
  • 程序:磁盘上的静态文件;进程:程序运行起来的实例。
  • 每个进程独立拥有:CPU 时间片、内存、文件描述符、堆、栈等资源。
  • 进程之间相互隔离,一个进程崩溃不会影响其他进程。
  • 进程切换开销很大:需要保存 / 恢复整个内存上下文、页表、寄存器等。
适用场景:CPU 密集型任务(计算、大数据运算)

2. 线程(Thread)

CPU 调度执行的最小单位(轻量级进程)
  • 线程依附于进程,一个进程至少有 1 个主线程。
  • 同一个进程内所有线程共享进程资源(全局变量、文件句柄等)。
  • 线程切换只需要切换少量寄存器,开销远小于进程。
  • 多线程并发,存在线程安全问题,需要锁(Lock)。
适用场景:IO 密集型(网络请求、文件读写、数据库操作)

3. 协程(Coroutine)

用户态的轻量级线程,由程序调度,操作系统无感知
  • 不是操作系统原生线程,在单线程内实现并发。
  • 切换只需要保存少量上下文,开销极小。
  • 遇到 IO 阻塞时主动让出 CPU,不用操作系统切换。
  • 无需锁机制,单线程执行,天然线程安全。
适用场景:超高并发 IO 密集型(爬虫、异步接口、高并发网络服务)

二、三者层级关系

进程 → 线程 → 协程
 
1 个进程可以包含多个线程;
 
1 个线程可以包含多个协程。

三、核心对比表

表格
 
维度进程线程协程
调度者 操作系统内核 操作系统内核 用户程序(代码手动调度)
资源 独立资源,进程间隔离 共享进程资源 共享线程资源
切换开销 最大 中等 极小
并发方式 多进程并行 多线程并发 单线程异步并发
安全问题 无共享,安全 共享全局变量,需要加锁 单线程执行,无需锁
适合场景 CPU 密集型 IO 密集型 高并发 IO 密集型
Python 限制 无 GIL 限制 受 GIL 限制,无法利用多核 单线程,无法利用多核

四、Python 经典难点:GIL 全局解释器锁

  1. GIL 是 CPython 独有锁,同一时刻一个进程内只能有一个线程执行 CPU 运算。
  2. 多线程在 CPU 密集场景下无法利用多核,本质串行,效率甚至不如单线程。
  3. IO 密集型时线程阻塞会释放 GIL,多线程可以提升效率。
  4. 想要利用多核:用多进程(每个进程有独立 GIL)。

场景选型总结

  1. CPU 密集型(矩阵运算、数据分析、加密) → 用 多进程 multiprocessing
  2. 普通 IO 密集型(少量爬虫、文件读写) → 用 多线程 threading
  3. 高并发 IO(上万并发爬虫、异步 Web) → 用 协程 asyncio

五、进程间通信 / 线程间通信

1. 线程间通信

  • 共享全局变量(不安全,必须加 threading.Lock
  • 队列:queue.Queue(线程安全)

2. 进程间不能直接共享全局变量(内存隔离)

常用方式:
  1. multiprocessing.Queue 队列
  2. Pipe 管道
  3. Manager 共享字典、列表
  4. 共享内存

六、协程关键知识点

  1. 协程是非抢占式调度:只有协程主动让出 CPU(await IO)才会切换;
     
    进程 / 线程是抢占式:操作系统时间片到了强制切换。
  2. 三大实现方式:
    • 生成器 yield 实现简易协程
    • async/await(Python3.5+ 官方标准协程)
    • gevent 第三方猴子补丁协程
  3. 协程缺点:
  • 一旦某个协程是 CPU 密集型阻塞,整个线程被卡死,其他协程无法调度;
  • 无法利用多核,想要多核 + 协程:多进程 + 每个进程内开协程。

七、高频面试简答题

1. 并发和并行区别

  • 并行:同一时刻多个任务同时执行(多核 CPU,多进程 / 多线程)
  • 并发:同一时间段交替执行,宏观同时,微观串行(单核多线程、协程)

2. 什么是僵尸进程、孤儿进程

  • 僵尸进程:子进程先结束,父进程没调用 wait () 回收退出状态,子进程残留 PCB。
  • 孤儿进程:父进程提前退出,子进程被系统 init 进程收养,不会成为僵尸。

3. 线程死锁产生四个必要条件

  1. 互斥条件
  2. 请求与保持
  3. 不可剥夺
  4. 循环等待
     
    解决:破坏任一条件,比如统一锁的获取顺序、设置超时释放锁。

八、三者简单代码示例

1. 多线程

python
 
运行
 
 
 
import threading

def task():
    print("线程任务")

t = threading.Thread(target=task)
t.start()
 

2. 多进程

python
 
运行
 
 
 
from multiprocessing import Process

def task():
    print("进程任务")

if __name__ == "__main__":
    p = Process(target=task)
    p.start()
 

3. 协程 async/await

python
 
运行
 
 
 
import asyncio

async def task():
    await asyncio.sleep(1)
    print("协程执行")

asyncio.run(task())
 

九、面试常问坑点

  1. Python 多线程为什么不能利用多核?GIL 限制。
  2. GIL 什么时候释放?CPU 时间片用完、遇到 IO 阻塞、手动 sleep。
  3. 协程需要加锁吗?单线程调度,同一时间只有一个协程运行,不需要互斥锁。
  4. 进程崩溃会带走里面所有线程;单个线程崩溃会导致整个进程崩溃。

进程、线程、协程 10 道高频面试真题(含答案 + 解析)

一、基础问答类

第 1 题:简单说说进程、线程、协程三者区别与联系?

参考答案:
  1. 进程:操作系统资源分配最小单位,进程间内存相互隔离,切换开销最大,一个进程至少包含一个主线程,适合 CPU 密集型任务,可利用多核。
  2. 线程:CPU 调度最小单位,依附进程,同进程内线程共享进程资源,切换开销中等;CPython 受 GIL 限制,多线程无法实现 CPU 密集并行,适合 IO 密集场景,多线程共享变量存在线程安全问题,需要加锁。
  3. 协程:用户态轻量级线程,单线程内实现并发,由程序手动调度,切换只需要保存少量上下文,开销极小;非抢占式调度,遇到 IO 主动让出 CPU,天然线程安全无需互斥锁,适合高并发 IO 场景,无法利用多核。
层级关系:一个进程可以有多个线程,一个线程可以运行多个协程。

第 2 题:并发和并行的区别?

参考答案:
  • 并行:多核 CPU 下,多个任务同一时刻同时执行,多进程可以实现并行。
  • 并发:单核 CPU 下,多个任务交替抢占时间片执行,宏观上同时运行,微观上串行;多线程、协程都属于并发。

第 3 题:什么是 GIL?GIL 带来什么影响?什么时候释放 GIL?

参考答案:
  1. GIL 全称全局解释器锁,是 CPython 解释器的一把互斥锁,保证同一时刻一个进程内只有一个线程执行 CPU 代码。
  2. 影响:
    • 多线程无法利用多核做 CPU 密集型并行计算,效率低下;
    • IO 密集场景下线程阻塞会释放 GIL,多线程可以提升效率。
  3. GIL 释放时机:
    • 线程遇到 IO 阻塞(网络、文件、sleep)自动释放;
    • 线程 CPU 执行时间片耗尽强制释放;
    • 手动调用特定方法主动释放。

二、场景选型题

第 4 题:CPU 密集、IO 密集分别用什么方案?为什么?

参考答案:
  1. CPU 密集(大数据计算、加密、图像处理)→ 多进程
     
    原因:规避 GIL 限制,每个进程拥有独立 GIL,可以利用多核并行计算;缺点是进程切换开销大,占用内存多。
  2. 普通 IO 密集(文件读写、数据库、少量接口请求)→ 多线程
     
    原因:IO 阻塞时释放 GIL,多线程并发执行,线程切换开销小,开发简单。
  3. 高并发 IO(爬虫、高并发 web 服务)→ 协程 asyncio
     
    原因:协程切换开销远小于线程,上万并发场景资源占用极低,单线程调度无需锁。

第 5 题:协程为什么不需要互斥锁?

参考答案:
 
协程属于非抢占式调度,同一线程内任意时刻只有一个协程在运行,只有当前协程主动遇到 IO、await 让出执行权时,才会切换其他协程,不会出现多个协程同时修改共享变量的情况,因此天然线程安全,不需要加互斥锁。

三、线程安全 & 锁类高频题

第 6 题:多线程共享全局变量为什么不安全?怎么解决?

参考答案:
  1. 不安全原因:线程执行过程中可能被 CPU 时间片强制切换,一个变量自增操作会被拆分为多条指令,切换后导致数据错乱。
  2. 解决方案:
  • 使用threading.Lock互斥锁,保证同一时间只有一个线程执行临界区代码;
  • 使用线程安全工具:queue.Queue队列;
  • 使用局部变量代替全局变量。

第 7 题:死锁产生的四个必要条件?如何避免死锁?

参考答案:
  1. 四个必要条件:
     
    ① 互斥条件:资源同一时间只能被一个线程占用;
     
    ② 请求并保持:线程持有已有资源,同时请求新资源不释放旧资源;
     
    ③ 不可剥夺:已占用资源不能被其他线程强制抢走;
     
    ④ 循环等待:多个线程形成首尾循环等待资源的闭环。
  2. 避免方案:
  • 破坏循环等待:所有线程按照固定顺序申请锁;
  • 设置锁超时时间,超时自动释放;
  • 尽量减少嵌套锁使用。

四、进程通信 & 面试易错坑题

第 8 题:进程间为什么不能直接共享全局变量?进程间有哪些通信方式?

参考答案:
  1. 每个进程拥有独立的内存空间,操作系统做了内存隔离,直接使用全局变量无法在多个进程间同步数据。
  2. 常用通信方式:
  • multiprocessing.Queue 安全队列;
  • Pipe 管道,适合两个进程双向通信;
  • Manager 管理器,共享列表、字典等数据结构;
  • 共享内存、文件、socket。
补充:线程间通信可以用全局变量 + 锁、queue.Queue

第 9 题:僵尸进程、孤儿进程是什么?怎么处理僵尸进程?

参考答案:
  1. 僵尸进程:子进程先执行结束退出,父进程没有调用wait()/waitpid()回收子进程的退出状态,子进程 PCB 进程控制块残留在内核中,占用系统资源。
  2. 孤儿进程:父进程提前退出,子进程会被操作系统的 init 进程收养,init 会自动回收资源,孤儿进程无害。
  3. 处理方式:父进程调用 wait 方法回收、设置信号处理机制自动回收子进程。

五、进阶综合题

第 10 题:协程能不能利用多核?想要多核 + 高并发 IO 怎么实现?

参考答案:
  1. 原生协程运行在单线程中,无法直接利用多核 CPU。
  2. 实现方案:使用多进程 + 每个进程内部开启协程,既通过多进程规避 GIL 利用多核,又借助协程实现单进程内高并发 IO,最大化并发性能。

附加 3 道手写代码面试题(简要考点)

  1. 用多线程实现多个任务并发,加锁保证变量安全
  2. 用多进程实现 CPU 密集计算,使用进程队列收集结果
  3. 用 asyncio 实现简单协程并发爬虫
posted @ 2020-05-19 20:09  sjxm2017  阅读(146)  评论(0)    收藏  举报