摘要:本文将分别介绍操作系统中的进程、线程和协程,重点关注其区别已经各自的适用场景。最后在python语言环境下,通过测试进程、线程和协程在不同任务下的执行效率来验证以上的观点。
关键词:进程、线程、协程、python
基本概念
进程
进程是操作系统分配资源的独立单位,进程创建和切换的开销较大。进程往往是一个独立的应用,不同进程之间资源相互隔离,因此进程适用于计算密集型应用,可以很好地适配多核CPU。以Python为例,由于全局解释器锁的存在,要想利用多核CPU带来的性能提升,最好使用多进程而不是多线程。进程还有一个好处就是稳定性高,一个进程崩溃了不会影响其他进程。著名的Apache服务器最早采用的就是多进程模式。
线程
线程则相对来说更加地轻量级,线程是CPU调度的最小单位,同一个进程的多个线程可以共享进程的资源,但是会有自己的栈空间,每一个线程的栈空间大概是2mb左右。线程适用于io密集型任务,且这些io任务的耗时比较短。选择运行多少线程数可以与任务的类型和cpu核心数相关,如果是计算密集型任务,线程数大致等于cpu核心数,如果是io密集型任务,线程数可以取cpu核心数的两倍加一,在java并发编程实战中给出了一个如下的公式:
N
t
h
r
e
a
d
=
N
c
p
u
∗
(
1
+
T
w
a
i
t
T
w
o
r
k
)
N_{thread}=N_{cpu} * (1+\frac{T_{wait}}{T_{work}})
Nthread=Ncpu∗(1+TworkTwait)
实际上,在linux系统中,没有对进程和线程进行太细致的区分,都是由一个名为task_struct的结构体来定义,创建新的进程一般使用fork调用,基于写时复制来解决创建子进程内存拷贝的消耗。线程的一个缺点就是稳定性差,由于一个进程的所有线程共享内存,如果一个线程崩溃了,可能整个进程都会崩溃。
协程
协程则是用户级别的轻量级线程,由于是在用户层面实现的,所以其没有用户态内核态之间切换的开销,但是需要在用户态下实现对于协程的管理和调度。协程适用于io密集型任务,尤其是在这些io耗时较长的情况下。
在golang语言里,通过GMP模型实现了协程的管理和调度,P的数量可以对应各个cpu核心,每一个P都有一个运行队列保存G协程,通过绑定系统线程来运行G。
在python语言中,协程通过关键字yield和send来实现,可以在一个线程中实现并发的生产者和消费者,但是协程的目的是为了能够在遇到io阻塞时,切换到另外一个任务执行,这个切换的过程应该由用户态控制,而不是消耗更大的内核态。因此,可以使用第三方库Gevent来实现遇到io阻塞时自动切换任务
实验
相关实验代码如下:
from multiprocessing import Process
import time
import threading
from gevent import monkey;monkey.patch_all()
import gevent
N = 1000_00
M = 20
# cpu_task定义cpu密集型任务
def cpu_task():
for i in range(N):
a = 13
b = 21
c = a + b
# io_task_short定义io密集型任务,且io操作耗时较短
def io_task_short():
for i in range(N // 100):
a = 1
time.sleep(0.01)
# io_task_long定义io密集型任务,且io操作耗时较长
def io_task_long():
for i in range(N // 1000):
a = 1
time.sleep(0.1)
if __name__ == "__main__":
for task in [cpu_task, io_task_short, io_task_long]:
t1 = time.time()
# 多进程
procs = []
for i in range(M):
p = Process(target=task)
p.start()
procs.append(p)
for p in procs:
p.join()
t2 = time.time()
# 多线程
threads = []
for i in range(M):
t = threading.Thread(target=task)
t.start()
threads.append(t)
for t in threads:
t.join()
t3 = time.time()
# 多协程
gevent.joinall([gevent.spawn(task) for i in range(M)])
t4 = time.time()
print(f"在{task.__name__}任务上,多进程运行时间:{t2 - t1}s,多线程运行时间:{t3 - t2}s,多协程运行时间{t4 - t3}s")
运行截图如下:

看起来运行效果一般,需要进一步排查分析。

被折叠的 条评论
为什么被折叠?



