进程(Process)
简单来说,进程是操作系统分配资源的基本单位。
单进程
-
当你双击一个 Python 程序:
bashpython main.py -
操作系统会创建一个进程:
bash操作系统 │ └── Python进程 ├── 代码 ├── 内存 ├── 文件句柄 ├── 网络连接 └── 线程 -
每个进程拥有独立的:
- 内存空间
- 全局变量
- 文件描述符
- 网络资源
-
例如:
pythona = 100 -
进程A:
pythona = 100 -
进程B:
pythona = 100看起来一样,但实际上存放在不同内存中
多进程
-
例如
pythonimport multiprocessing import os # 进程要执行的任务 def task(num): print(f"进程{num} 启动,PID:{multiprocessing.current_process().pid},PPID:{os.getppid()}") if __name__ == "__main__": process_list = [] # 创建3个进程 for i in range(1, 4): p = multiprocessing.Process(target=task, args=(i,)) process_list.append(p) p.start() # 启动进程 # 主进程等待3个子进程全部执行完毕 for p in process_list: p.join() print(f"所有3个子进程运行结束,主进程退出,主进程PID:{os.getpid()}") # 打印结果: # 进程1 启动,PID:20498,PPID:20496 # 进程2 启动,PID:20499,PPID:20496 # 进程3 启动,PID:20500,PPID:20496 # 所有3个子进程运行结束,主进程退出,主进程PID:20496 -
优点
-
真正并行
bashCPU核心1 -> 进程1 CPU核心2 -> 进程2 CPU核心3 -> 进程3
-
-
缺点
-
创建成本高
bash例如: 内存: 100MB 启动: 几十毫秒
-
线程(Thread)
简单来说,线程是 CPU 调度的基本单位。
单线程
-
线程存在于进程内部。
-
例如:
pythonPython进程 │ ├── 主线程 ├── 线程1 ├── 线程2 └── 线程3 -
线程共享同一个进程的资源:
bash进程内存 │ ├── 全局变量 ├── 堆 ├── 文件句柄 └── socket -
所有线程都能访问
-
例如:
pythoncount = 0 -
线程1:
pythoncount += 0 -
线程2:
pythoncount += 0都在修改同一个变量。
多线程
-
例如
pythonfrom threading import Thread -
创建
bashPython进程 │ ├── Thread1 ├── Thread2 └── Thread3 -
优点
- 创建快
- 切换快
- 共享数据方便
-
缺点
-
多个线程修改同一个变量时容易出问题
pythoncount += 1 -
可能变成
bash线程1读取 count=0 线程2读取 count=0 线程1写回 1 线程2写回 1 -
结果
bash应该是2 实际是1 -
线程安全问题(Race Condition)
- 可以使用互斥锁解决,在这里不过多赘述,主要研究协程和事件循环
-
进程和线程的关系
可以把它想成公司:
-
进程
bash公司A 公司B 公司C每个公司独立。
-
线程
bash公司A │ ├── 员工1 ├── 员工2 └── 员工3员工共享公司的资源。
所以,层级关系是这样的:
bash
进程
└── 线程
└── 协程
协程(Coroutine)
- 线程切换
- 操作系统决定
- 协程切换
-
程序员决定
-
例如:
pythonawait asyncio.sleep(1) # 这里主动告诉事件循环 我先暂停 你去执行别的协程
-
协程和线程的关系
-
线程
bash线程1 线程2 线程3 操作系统切换 -
协程
bash协程1 协程2 协程3 EventLoop切换 -
线程切换需要(成本高)
bash保存CPU寄存器 切换内核态 恢复现场 -
协程切换只需要(成本极低)
bash保存函数执行位置
操作系统、进程、线程和协程四者关系图
bash
操作系统
│
├── 进程A
│ │
│ ├── 线程1
│ │ │
│ │ ├── 协程1
│ │ ├── 协程2
│ │ └── 协程3
│ │
│ └── 线程2
│
└── 进程B
│
└── 线程1