详解Python并发编程之从性能角度来初探并发编程


Posted in Python onAugust 23, 2019

. 前言

作为进阶系列的一个分支「并发编程」,我觉得这是每个程序员都应该会的。

并发编程 这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。

昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整):

详解Python并发编程之从性能角度来初探并发编程

对于并发编程,Python的实现,总结了一下,大致有如下三种方法:

  • 多线程
  • 多进程
  • 协程(生成器)

在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。

. 并发编程的基本概念

在开始讲解理论知识之前,先过一下几个基本概念。虽然咱是进阶教程,但我也希望写得更小白,更通俗易懂。

  • 串行:一个人在同一时间段只能干一件事,譬如吃完饭才能看电视;
  • 并行:一个人在同一时间段可以干多件事,譬如可以边吃饭边看电视;

在Python中,多线程 和 协程 虽然是严格上来说是串行,但却比一般的串行程序执行效率高得很。一般的串行程序,在程序阻塞的时候,只能干等着,不能去做其他事。就好像,电视上播完正剧,进入广告时间,我们却不能去趁广告时间是吃个饭。对于程序来说,这样做显然是效率极低的,是不合理的。

当然,学完这个课程后,我们就懂得,利用广告时间去做其他事,灵活安排时间。这也是我们多线程和协程 要帮我们要完成的事情,内部合理调度任务,使得程序效率最大化。

虽然 多线程 和 协程 已经相当智能了。但还是不够高效,最高效的应该是一心多用,边看电视边吃饭边聊天。这就是我们的 多进程 才能做的事了。

为了更帮助大家更加直观的理解,在网上找到两张图,来生动形象的解释了多线程和多进程的区别。(侵删)

多线程,交替执行,另一种意义上的串行。

详解Python并发编程之从性能角度来初探并发编程

多进程,并行执行,真正意义上的并发。

详解Python并发编程之从性能角度来初探并发编程

. 单线程VS多线程VS多进程

文字总是苍白无力的,千言万语不如几行代码来得孔武有力。

首先,我的实验环境配置如下

操作系统 CPU核数 内存(G) 硬盘
CentOS 7.2 24核 32 机械硬盘

注意以下代码,若要理解,对小白有如下知识点要求:

  • 装饰器的运用
  • 多线程的基本使用
  • 多进程的基本使用

当然,看不懂也没关系,主要最后的结论,能让大家对单线程、多线程、多进程在实现效果上有个大体清晰的认识,达到这个效果,本文的使命也就完成了,等到最后,学完整个系列,不妨再回头来理解也许会有更深刻的理解。

下面我们来看看,单线程,多线程和多进程,在运行中究竟孰强孰弱。

开始对比之前,首先定义四种类型的场景

  • CPU计算密集型
  • 磁盘IO密集型
  • 网络IO密集型
  • 【模拟】IO密集型

为什么是这几种场景,这和多线程 多进程的适用场景有关。结论里,我再说明。

# CPU计算密集型
def count(x=1, y=1):
  # 使程序完成150万计算
  c = 0
  while c < 500000:
    c += 1
    x += x
    y += y


# 磁盘读写IO密集型
def io_disk():
  with open("file.txt", "w") as f:
    for x in range(5000000):
      f.write("python-learning\n")


# 网络IO密集型
header = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'}
url = "https://www.tieba.com/"

def io_request():
  try:
    webPage = requests.get(url, headers=header)
    html = webPage.text
    return
  except Exception as e:
    return {"error": e}


# 【模拟】IO密集型
def io_simulation():
  time.sleep(2)

比拼的指标,我们用时间来考量。时间耗费得越少,说明效率越高。

为了方便,使得代码看起来,更加简洁,我这里先定义是一个简单的 时间计时器 的装饰器。如果你对装饰器还不是很了解,也没关系,你只要知道它是用于 计算函数运行时间的东西就可以了。

def timer(mode):
  def wrapper(func):
    def deco(*args, **kw):
      type = kw.setdefault('type', None)
      t1=time.time()
      func(*args, **kw)
      t2=time.time()
      cost_time = t2-t1
      print("{}-{}花费时间:{}秒".format(mode, type,cost_time))
    return deco
  return wrapper

第一步,先来看看单线程的

@timer("【单线程】")
def single_thread(func, type=""):
  for i in range(10):
       func()

# 单线程
single_thread(count, type="CPU计算密集型")
single_thread(io_disk, type="磁盘IO密集型")
single_thread(io_request,type="网络IO密集型")
single_thread(io_simulation,type="模拟IO密集型")

看看结果

【单线程】-CPU计算密集型花费时间:83.42633867263794秒
【单线程】-磁盘IO密集型花费时间:15.641993284225464秒
【单线程】-网络IO密集型花费时间:1.1397218704223633秒
【单线程】-模拟IO密集型花费时间:20.020972728729248秒

第二步,再来看看多线程的

@timer("【多线程】")
def multi_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

# 多线程
multi_thread(count, type="CPU计算密集型")
multi_thread(io_disk, type="磁盘IO密集型")
multi_thread(io_request, type="网络IO密集型")
multi_thread(io_simulation, type="模拟IO密集型")

看看结果

【多线程】-CPU计算密集型花费时间:93.82986998558044秒
【多线程】-磁盘IO密集型花费时间:13.270896911621094秒
【多线程】-网络IO密集型花费时间:0.1828296184539795秒
【多线程】-模拟IO密集型花费时间:2.0288875102996826秒

第三步,最后来看看多进程

@timer("【多进程】")
def multi_process(func, type=""):
  process_list = []
  for x in range(10):
    p = Process(target=func, args=())
    process_list.append(p)
    p.start()
  e = process_list.__len__()

  while True:
    for pr in process_list:
      if not pr.is_alive():
        e -= 1
    if e <= 0:
      break

# 多进程
multi_process(count, type="CPU计算密集型")
multi_process(io_disk, type="磁盘IO密集型")
multi_process(io_request, type="网络IO密集型")
multi_process(io_simulation, type="模拟IO密集型")

看看结果

【多进程】-CPU计算密集型花费时间:9.082211017608643秒
【多进程】-磁盘IO密集型花费时间:1.287339448928833秒
【多进程】-网络IO密集型花费时间:0.13074755668640137秒
【多进程】-模拟IO密集型花费时间:2.0076842308044434秒

. 性能对比成果总结

将结果汇总一下,制成表格。

种类 CPU 计算密集型 磁盘 IO密集型 网络 IO密集型 模拟 IO密集型
单线程 83.42 15.64 1.13 20.02
多线程 93.82 13.27 0.18 2.02
多进程 9.08 1.28 0.13 2.01

我们来分析下这个表格。

首先是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资源支持下才能体现优势

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python完成FizzBuzzWhizz问题(拉勾网面试题)示例
May 05 Python
Python实现比较两个文件夹中代码变化的方法
Jul 10 Python
TensorFlow Session会话控制&amp;Variable变量详解
Jul 30 Python
pygame编写音乐播放器的实现代码示例
Nov 19 Python
python3操作注册表的方法(Url protocol)
Feb 05 Python
python如何实现单链表的反转
Feb 10 Python
Keras SGD 随机梯度下降优化器参数设置方式
Jun 19 Python
Python常用库Numpy进行矩阵运算详解
Jul 21 Python
解决python 输出到csv 出现多空行的情况
Mar 24 Python
python tkinter模块的简单使用
Apr 07 Python
手把手教你使用TensorFlow2实现RNN
Jul 15 Python
Python torch.flatten()函数案例详解
Aug 30 Python
python Kmeans算法原理深入解析
Aug 23 #Python
Python高级特性 切片 迭代解析
Aug 23 #Python
Python 合并多个TXT文件并统计词频的实现
Aug 23 #Python
Python 调用 Windows API COM 新法
Aug 22 #Python
详解Python文件修改的两种方式
Aug 22 #Python
详解python中的生成器、迭代器、闭包、装饰器
Aug 22 #Python
python支付宝支付示例详解
Aug 22 #Python
You might like
YB217、YB235、YB400浅听
2021/03/02 无线电
新版mysql+apache+php Linux安装指南
2006/10/09 PHP
php foreach、while性能比较
2009/10/15 PHP
20个PHP常用类库小结
2011/09/11 PHP
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
php防止sql注入简单分析
2015/03/18 PHP
JS字符串截取函数实例
2013/12/27 Javascript
javascript实现禁止右键和F12查看源代码
2014/12/26 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery中toggleClass()方法用法实例
2015/01/05 Javascript
JS实现1000以内被3或5整除的数字之和
2016/02/18 Javascript
Easyui Treegrid改变默认图标的方法
2016/04/29 Javascript
JavaScript中一些特殊的字符运算
2017/08/17 Javascript
javascript实现电脑和手机版样式切换
2017/11/10 Javascript
关于jquery中attr()和prop()方法的区别
2018/05/28 jQuery
如何手动实现es5中的bind方法详解
2018/12/07 Javascript
Vue基于iview table展示图片实现点击放大
2020/08/05 Javascript
Python多进程通信Queue、Pipe、Value、Array实例
2014/11/21 Python
Python中多线程的创建及基本调用方法
2016/07/08 Python
Python使用装饰器模拟用户登陆验证功能示例
2018/08/24 Python
python机器学习之KNN分类算法
2018/08/29 Python
Python线程同步的实现代码
2018/10/03 Python
python实现朴素贝叶斯算法
2018/11/19 Python
对Python的交互模式和直接运行.py文件的区别详解
2019/06/29 Python
Python字典添加,删除,查询等相关操作方法详解
2020/02/07 Python
Django中使用Celery的方法步骤
2020/12/07 Python
pandas统计重复值次数的方法实现
2021/02/20 Python
Html5中的桌面通知Notification的实现
2018/09/25 HTML / CSS
芝加哥牛排公司:Chicago Steak Company
2018/10/31 全球购物
给分销商的致歉信
2014/01/14 职场文书
2014年创卫实施方案
2014/02/18 职场文书
村主任“四风”问题个人整改措施
2014/10/04 职场文书
初中班干部工作总结
2015/08/10 职场文书
导游词之韩国济州岛
2019/10/28 职场文书
OpenCV-Python实现油画效果的实例
2021/06/08 Python
python使用pycharm安装pyqt5以及相关配置
2022/04/22 Python