Python并发支持
(1)多线程与多进程对比
(2)IO密集型与CPU密集型计算对比
(3)并发编程-常见问题
并发编程中,还有几个绕不开的话题:
使用Lock对资源加锁,防止并发冲突。
使用队列Queue可以实现线程或进程间通信,可以实现生产者-消费者模式
使用线程池或进程池,简化线程或者进程的提交、等待与获取结果。
Python速度慢的原因
在并发编程上,Python比C/C++、Java都慢。主要有以下原因:
解释性语言
1、边解释边执行
Python是动态类型的语言,需要边解释边执行。
C/C++编写完成之后,需要编译为直接可执行的机器码,机器码执行速度非常快。
2、变量的特性
再次就是Python中变量的类型,是不固定的。
它既可以是数字,随时可以切换为字符串或者列表。
这就需要随时检查变量数据类型,所以性能下降。
GIL(全局解释器锁)
全局解释器锁(GIL)导致了Python多线程无法利用多核CPU并发执行。
全局解释器锁(GIL:Global Interpreter Lock的缩写)。
GIL是Python解释器用于同步线程的一种机制,使得任何时刻仅有一个线程在执行;即便在多核CPU上,GIL的解释器也只允许同一时间执行一个线程。
最开始引入GIL,是为了解决多线程之间数据完整性和状态同步的问题,简化了Python对共享资源的管理;但是也降低了并发编程的性能。现在想要去除,却比较难了。
CPU密集型计算案例
CPU密集型计算
key = 100000000 * 100000
num_list = [random.randint(key, 10 * key) for i in range(1000)]
# 计算一个数是否是质数
def is_prime(num: int) -> bool:
if num < 2:
return False
if num == 2:
return True
if num % 2 == 0:
return False
sqrt_num = int(math.floor(math.sqrt(num)))
for i in range(3, sqrt_num + 1, 2):
if num % i == 0:
return False
return True
这里定义一个判断质数的方法,判断1000个数(10万亿 ~ 100万亿之间的随机数)
三种方式对比
单线程、多线程、多进程处理这个CPU密集型计算;统计三种方法耗时。
# 单线程处理
def single_thread():
for num in num_list:
is_prime(num)
# 多线程处理
def multi_threads():
with ThreadPoolExecutor() as pool:
pool.map(is_prime, num_list)
# 多进程处理
def multi_process():
with ProcessPoolExecutor() as pool:
pool.map(is_prime, num_list)
主执行方法
# 处理耗时统计
if __name__ == "__main__":
start = time.time()
single_thread()
end = time.time()
print(f"single thread cost : {end - start}")
start = time.time()
multi_threads()
end = time.time()
print(f"multi threads cost : {end - start}")
start = time.time()
multi_process()
end = time.time()
print(f"multi process cost : {end - start}")
执行结果对比
single thread cost : 8.104012489318848
multi threads cost : 8.150990724563599
multi process cost : 1.85487961769104
结论:
对于CPU密集型任务,
(1)多线程可能因为线程切换,比单线程性能还差。
(2)多进程性能明显优于多线程。