Python进程、线程、协程、GIL
Python进程、线程、协程、GIL
基础概念
进程(Process)
进程是操作系统分配资源的基本单位,每个进程都有自己的内存空间、代码段、数据段、堆栈等。进程的特点是:
- 独立性强:一个进程崩溃不会直接影响其他进程。
- 系统开销大:创建和切换进程需要操作系统切换上下文和内存管理。
- 进程间通信(IPC)复杂:常用方法有管道、消息队列、共享内存、socket等。
线程(Thread)
线程是CPU调度的基本单位,是比进程更小的执行单元。线程属于进程,多个线程共享进程的内存空间。线程的特点是:
- 轻量级:创建、切换开销比进程小。
- 数据共享:同一进程内线程共享全局变量和堆内存,但要注意线程安全(加锁)。
- 受GIL限制:Python中同一时刻只能有一个线程执行Python字节码,因此CPU 密集型任务无法通过多线程实现真正并行。
协程(Coroutine)
协程是一种用户态的轻量级线程,也叫微线程,特点是非抢占式调度。协程依赖事件循环,在I/O等待时主动让出控制权,节省线程切换开销,协程只在一个线程中运行,但可以同时处理大量I/O任务。协程的特点是:
- 极轻量:成千上万协程在一个线程中也不占用大量内存。
- 非抢占式:需要显式await或yield才会切换。
- 适合I/O密集型任务。
全局解释器锁(GIL)
GIL是Python的一把锁,用来保证同一时刻只有一个线程执行Python 字节码。GIL存在是为了简化内存管理。GIL会导致多线程无法利用多核CPU做CPU密集型运算,I/O 密集型任务不受影响。
具体实现
使用
multiprocessing实现多进程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import multiprocessing
import os
import time
def process_task(task_name: str):
print(f"Process {task_name} started,PID: {os.getpid()},Time:{time.time()}")
time.sleep(5)
print(f"Process {task_name} finished,time:{time.time()}")
'''
-------------start-----------------
父进程
|
|-- p.start()
|
操作系统:
├─ 创建新进程(PID 不同)
├─ 分配独立内存空间
├─ 拷贝/初始化运行环境
└─ 调度子进程执行
-------------join-----------------
父进程:WAITING
子进程:RUNNING
↓
子进程退出
↓
父进程恢复执行
'''
if __name__ == "__main__":
tasks = ["Task 1", "Task 2", "Task 3"]
processes = []
for task in tasks:
p = multiprocessing.Process(target=process_task, args=(task,))
processes.append(p)
p.start() # 让OS调度进程,创建并启动一个并发执行单元
for p in processes:
p.join() # 阻塞当前进程(通常是父进程),直到子进程结束output:
1
2
3
4
5
6Process Task 2 started,PID: 5936,Time:1768805990.6005492
Process Task 1 started,PID: 5935,Time:1768805990.600867
Process Task 3 started,PID: 5937,Time:1768805990.602313
Process Task 2 finished,time:1768805995.602974
Process Task 1 finished,time:1768805995.605867
Process Task 3 finished,time:1768805995.608462使用
threading创建多线程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import threading
import os
import time
def thread_task(task_name: str):
print(f"Thread {task_name} started,PID: {os.getpid()},Time:{time.time()}")
time.sleep(5)
print(f"Thread {task_name} finished,time:{time.time()}")
'''
-------------start-----------------
进程
├─ 主线程
└─ 子线程(共享内存)
start():
├─ 创建 OS 线程
├─ 注册到当前进程
└─ 等待 CPU 调度
-------------join-----------------
主线程:WAITING
子线程:RUNNING
↓
子线程结束
↓
主线程继续
'''
if __name__ == "__main__":
tasks = ["Task 1", "Task 2", "Task 3"]
threads = []
for task in tasks:
t = threading.Thread(target=thread_task, args=(task,))
threads.append(t) # 在当前进程内创建一个新的执行线程,并执行task
t.start()
for t in threads:
t.join() # 阻塞当前线程,直到目标线程执行结束output:
1
2
3
4
5
6Thread Task 1 started,PID: 6098,Time:1768806447.717162
Thread Task 2 started,PID: 6098,Time:1768806447.717246
Thread Task 3 started,PID: 6098,Time:1768806447.717311
Thread Task 1 finished,time:1768806452.721641
Thread Task 2 finished,time:1768806452.724179
Thread Task 3 finished,time:1768806452.724231使用asyncio实现协程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46import asyncio
import time
async def task(task_name: str):
print(f"task {task_name} start,time:{time.time()}")
await asyncio.sleep(5)
print(f"task {task_name} end,time:{time.time()}")
'''
asyncio.run()
├─ 创建事件循环
├─ 创建 main Task
├─ 启动事件循环
└─ main 执行
└─ await gather
├─ 注册 task1/2/3
├─ 并发调度
└─ 等待全部完成
├─ main 结束
└─ 关闭事件循环
'''
async def main():
tasks = [task(str(i)) for i in range(3)]
await asyncio.gather(*tasks)
'''
协程对象:
→ create_task()
→ Task(Future)
→ 注册到事件循环
gather:
创建一个新的 Future
监听所有子 Task
等全部完成
收集结果
--------------时间线---------------
A start ─┐
B start ─┼── 并发执行
C start ─┘
↓
await(让出控制权)
↓
事件循环调度
'''
asyncio.run(main())output:协程的输出几乎同时开始,节省了线程的切换开销,非常高效
1
2
3
4
5
6task 0 start,time:1768807095.176304
task 1 start,time:1768807095.176335
task 2 start,time:1768807095.176345
task 0 end,time:1768807100.1782029
task 1 end,time:1768807100.178287
task 2 end,time:1768807100.178303线程安全加锁的实现
- 同步锁
Lock,不支持多次申请和多次释放 - 递归锁
RLock支持多次申请锁acquire()和多次释放锁release()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import threading
number = 0
thread_lock = threading.RLock() # 递归锁
def task_add():
# 使用线程锁上下文申请锁
with thread_lock:
global number
for i in range(1000):
number += i
print(f"Number is {number},Thread name is {threading.current_thread().name}")
def task_sub():
with thread_lock:
global number
for i in range(1000):
number -= i
print(f"Number is {number},Thread name is {threading.current_thread().name}")
add_thread = threading.Thread(target=task_add)
add_thread.name = "add_thread"
sub_thread = threading.Thread(target=task_sub)
sub_thread.name = "sub_thread"
add_thread.start()
sub_thread.start()output:
1
2Number is 499500,Thread name is add_thread
Number is 0,Thread name is sub_thread- 同步锁
Python进程、线程、协程、GIL
