在介绍python并发编程前先介绍下并行与并发得概念
并发:指一个处理器同时处理多个任务,例如一共两个任务,看电视和学习,你在一边学习一边看电视,大概是学习一会儿看一会电视的状态
并行:指多个处理器或者是多核的处理器同时处理多个不同的任务,例如一共两个任务,玩手机和学习,你负责玩手机,学霸负责学习,这是对任务的并行处理
并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。
上面的例子举得不好,看起来并发并不能提高任务的效率,其实不是这样的,具体情况是看电视时碰到了广告,你去学习,而不是等待广告结束,这样时间就被利用起来了,也就是在一个任务遇到了瓶颈先去做另一个任务,而不是原地等待
python并发编程也大概是这个意思,在程序中有线程和进程两个概念,程序运行的效率也可以多线程和多进程两个方面去提升
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
进程与线程的区别:
-
地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。
-
资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。
-
健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
-
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
-
切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
从上面的区别可以搞清楚什么时候要用多线程什么时候用多进程
但python还有一个特性,就是python的GIL锁,锁在很多编程语言中都有出现,前面提到了线程会共享本进程的资源,因为在并发编程中会对多个线程会对同一个资源进行操作,这时会出现线程安全问题,锁就是用来解决这个问题的,类似于数据库中的脏数据,而python的GIL锁在任意时刻只允许一个 Python 进程使用 Python 解释器。也就是任意时刻,Python 只有一个线程在运行。
看到这里很多人会感到疑惑,如果在任意时刻,Python 只有一个线程在运行,那多线程不就没有什么意义了
其实不是这样的,准确的说,由于GIL的机制,单核CPU在同一时刻只有一个线程在运行。但当线程遇到IO操作或Timer Tick到期,释放GIL锁,那么Time Tick到期是什么呢?Time Tick规定了线程的最长执行时间,超过时间后自动释放GIL锁。
所以在某些场景下,python多线程对程序性能还是有很大提升的
程序的瓶颈一般是计算和IO等耗时操作,所以可以把任务分为计算密集型和IO密集型。
计算密集型:一般是指服务器的硬盘、内存硬件性能相对CPU好很多,或者使用率低很多。系统运行CPU读写I/O(硬盘/内存)时可以在很短的时间内完成,几乎没有阻塞(等待I/O的实时间)时间,而CPU一直有大量运算要处理,因此CPU负载长期过高。
IO密集型:一般是指服务器CPU的性能相对硬盘、内存硬件好很多,或者使用率低很多。系统运行多是CPU在等I/O (硬盘/内存) 的读写操作,此类情景下CPU负载并不高。
总结,由于python的GIL锁,如果程序是计算密集型,使用多线程是对代码不会有太大帮助的,而应该使用多进程,如果程序是IO密集型,那么优先使用多线程,因为多进程的资源开销更大
如下经典示例代码,此代码的瓶颈主要在计算而非IO
未使用多线程、多进程
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
start = time.time()
countdown(COUNT)
end = time.time()
print('Time taken in seconds -', end - start)
耗时
Time taken in seconds - 2.205103874206543
使用多线程
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
t1 = Thread(target=countdown, args=(COUNT//2,))
t2 = Thread(target=countdown, args=(COUNT//2,))
start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print('Time taken in seconds -', end - start)
耗时
Time taken in seconds - 2.226524829864502
可以看到,性能基本没有提升,还慢了一点点,考虑到误差,两者性能基本一样
from multiprocessing import Pool
import time
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
if __name__ == '__main__':
pool = Pool(processes=2)
start = time.time()
r1 = pool.apply_async(countdown, [COUNT//2])
r2 = pool.apply_async(countdown, [COUNT//2])
pool.close()
pool.join()
end = time.time()
print('Time taken in seconds -', end - start)
耗时
Time taken in seconds - 1.4109129905700684
可以看到多进程对与计算密集型程序性能提升还是非常明显的,如果你觉得不够明显,可以把计算量调大,就是代码中的COUNT值,效果会更加明显