Python 实现并发编程
多线程
多过程
协程 (生成器)
并发编程的基本概念
串行:一个人在一段时间段内只能干一件事件(吃完饭后能力看电视)
并行:一个人在一段时间内同时干多件事件(边吃饭边看电视)
在 Python 中,多线程 和 协程 尽管是严格上来说是串行,但却比个别的串行程序执行效率高得很。
个别的串行程序,在程序阻塞的时候,只能干等着,不能去做其余事。就如同,电视上播完正剧,进入广告工夫,咱们却不能去趁广告工夫是吃个饭。对于程序来说,这样做显然是效率极低的,是不合理的。
当然,利用广告工夫去做其余事,灵便安顿工夫。这也是咱们多线程和协程 要帮咱们要实现的事件,外部正当调度工作,使得程序效率最大化。
尽管 多线程 和 协程 曾经相当智能了。但还是不够高效,最高效的应该是一心多用,边看电视边吃饭边聊天。这就是咱们的 多过程 能力做的事了。
单线程、多线程、多过程比照
试验配置
操作系统 cpu 核数 内存 硬盘
ubuntu 18.04 4 8G SSD
【文章福利】须要 C /C++ Linux 服务器架构师学习材料加群 1106747042(材料包含 C /C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等)
开始比照之前定义四种类型的场景
CPU 计算密集型
一些过程绝大多数工夫在计算上,称为计算密集型(CPU 密集型)computer-bound。一些大量循环的代码(例如:图片解决、视频编码、人工智能等)就是 CPU 密集型
磁盘 IO 密集
磁盘 io,顾名思义就是磁盘的输入输出。即向磁盘写入数据和从磁盘读取数据
网络 IO 密集
有一些过程则在 input 和 output 上破费了大多工夫,称为 I / O 密集型,I/O-bound。比方搜寻 引擎大多工夫是在期待相应
【模仿】IO 密集
import time
import requests
CPU 计算密集型
def cpu_count(a=1,b=1):
# 使程序实现 150 万次计算
c = 0
while c < 500000:
c += 1
a += a
b += b
磁盘读写 IO 密集
def io_disk():
with open('./IOtest.txt','w') as f:
for i in range(5000000):
f.write('磁盘 -io- 测试')
网络 IO 密集型
headers = {
"User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)"
}
url = ‘https://www.baidu.com/’
def io_request():
try:
response = requests.get(url,headers=headers)
return
except Except as e:
return e
模仿 IO 密集
def io_simulation():
time.sleep(2)
比拼的指标,用工夫来掂量。工夫耗费得越少,阐明效率越高
import functools import wraps
定义一个工夫装璜器,来计算耗费的工夫
def record(output):
def use_time(func):
@wraps(func)
def wrapper(*args,**kwargs):
type = kwargs.setdefault('type',None)
st_time = time.time()
result = func(*args,**kwargs)
end_time = time.time()
print(f'{output}-{type}耗时:{end_time-st_time}s')
return wrapper
return use_time
1. 先来看看单线程
定义单线程
@record(‘【单线程】’)
def single_thread(func,type=”):
for i in range(10):
func()
开始测试【单线程】
single_thread(cpu_count,type=’CPU 计算密集型 ’)
single_thread(io_disk,type=’ 磁盘 IO 密集型 ’)
single_thread(io_request,type=’ 网络 IO 密集型 ’)
single_thread(io_simulation,type=’ 模仿 IO 密集型 ’)
后果
【单线程】-CPU 计算密集型耗时:80.91554880142212s
【单线程】- 磁盘 IO 密集型耗时:15.750258445739746s
【单线程】- 网络 IO 密集型耗时:2.640401840209961s
【单线程】- 模仿 IO 密集型耗时:20.018026113510132s
2. 再来看看多线程
from threading import Thread
写法一
@record(‘【多线程】’)
def multi_thread(func,type=”):
threads = [Thread(target=func) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
多线程测试 1
multi_thread(cpu_count,type=’CPU 计算密集型 ’)
multi_thread(io_disk,type=’ 磁盘 IO 密集型 ’)
multi_thread(io_request,type=’ 网络 IO 密集型 ’)
multi_thread(io_simulation,type=’ 模仿 IO 密集型 ’)
后果
【多线程】-CPU 计算密集型耗时:121.99781441688538s
【多线程】- 磁盘 IO 密集型耗时:21.91859245300293s
【多线程】- 网络 IO 密集型耗时:0.4386627674102783s
【多线程】- 模仿 IO 密集型耗时:2.0033113956451416s
写法二(要慢一点)
@record(‘【多线程】’)
def mul_thread(func,type=”):
thread_list = []
for i in range(10):
t = Thread(target=func,args=())
thread_list.append(t)
t.start()
e = len(thread_list)
while True:
for th in thread_list:
if not th.is_alive():
e -= 1
if e <= 0:
break
多线程测试 2
mul_thread(cpu_count,type=’CPU 计算密集型 ’)
mul_thread(io_disk,type=’ 磁盘 IO 密集型 ’)
mul_thread(io_request,type=’ 网络 IO 密集型 ’)
mul_thread(io_simulation,type=’ 模仿 IO 密集型 ’)
后果
【多线程】-CPU 计算密集型耗时:126.94713139533997s
【多线程】- 磁盘 IO 密集型耗时:37.09427046775818s
【多线程】- 网络 IO 密集型耗时:0.6191723346710205s
【多线程】- 模仿 IO 密集型耗时:2.0074384212493896s
3. 最初来看看多过程吧
from multiprocessing import Process
写法一
@record(‘【多过程】’)
def multi_process(func,type=”):
processes = [Process(target=func) for _ in range(10)]
for process in processes:
process.start()
for process in processes:
process.join()
多过程测试1
multi_process(cpu_count,type=’CPU 计算密集型 ’)
multi_process(io_disk,type=’ 磁盘 IO 密集型 ’)
multi_process(io_request,type=’ 网络 IO 密集型 ’)
multi_process(io_simulation,type=’ 模仿 IO 密集型 ’)
后果
【多过程】-CPU 计算密集型耗时:44.23211455345154s
【多过程】- 磁盘 IO 密集型耗时:7.884604215621948s
【多过程】- 网络 IO 密集型耗时:0.40494322776794434s
【多过程】- 模仿 IO 密集型耗时:2.064232349395752s
写法二(还是要慢一点)
@record(‘【多线程】’)
def mul_process(func,type=”):
process_list = []
for i in range(10):
p = Process(target=func,args=())
process_list.append(p)
p.start()
e = len(process_list)
while True:
for p in process_list:
if not p.is_alive():
e -= 1
if e <= 0:
break
多过程测试2
mul_process(cpu_count,type=’CPU 计算密集型 ’)
mul_process(io_disk,type=’ 磁盘 IO 密集型 ’)
mul_process(io_request,type=’ 网络 IO 密集型 ’)
mul_process(io_simulation,type=’ 模仿 IO 密集型 ’)
后果
【多过程】-CPU 计算密集型耗时:47.9653902053833s
【多过程】- 磁盘 IO 密集型耗时:9.239834308624268s
【多过程】- 网络 IO 密集型耗时:0.31076598167419434s
【多过程】- 模仿 IO 密集型耗时:2.0489490032196045s
性能比照汇总
类型 cpu 计算密集型 磁盘 IO 密集型 网络 IO 密集型 模仿 IO 密集型
单线程 89.91 15.75 2.64 20.01
多线程测试一 121.99 21.91 0.43 2.00
多线程测试二 126.94 37.09 0.61 2.00
多过程测试一 44.23 7.88 0.40 2.06
多线程测试二 47.96 9.23 0.31 2.04
剖析下这个表格
首先是 CPU 密集型,多线程以比照单线程,不仅没有劣势,显然还因为要一直的加锁开释 GIL 全局锁,切换线程而消耗大量工夫,效率低下,而多过程,因为是多个 CPU 同时进行计算工作,相当于十个人做一个人的作业,显然效率是成倍增长的。
而后是 IO 密集型,IO 密集型能够是磁盘 IO,网络 IO,数据库 IO 等,都属于同一类,计算量很小,次要是 IO 等待时间的节约。通过观察,能够发现,咱们磁盘 IO,网络 IO 的数据,多线程比照单线程也没体现出很大的劣势来。这是因为咱们程序的的 IO 工作不够沉重,所以劣势不够显著。
所以我还加了一个「模仿 IO 密集型」,用 sleep 来模仿 IO 等待时间,就是为了体现出多线程的劣势,也能让大家更加直观的了解多线程的工作过程。单线程须要每个线程都要 sleep(2),10 个线程就是 20s,而多线程,在 sleep(2)的时候,会切换到其余线程,使得 10 个线程同时 sleep(2),最终 10 个线程也就只有 2s.
能够得出以下几点论断
单线程总是最慢的,多过程总是最快的。
多线程适宜在 IO 密集场景下应用,譬如爬虫,网站开发等
多过程适宜在对 CPU 计算运算要求较高的场景下应用,譬如大数据分析,机器学习等
多过程尽管总是最快的,然而不肯定是最优的抉择,因为它须要 CPU 资源反对下能力体现劣势