详解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实现给qq邮箱发送邮件的方法
May 28 Python
Python导入模块时遇到的错误分析
Aug 30 Python
Python读取properties配置文件操作示例
Mar 29 Python
Django 反向生成url实例详解
Jul 30 Python
PyTorch的SoftMax交叉熵损失和梯度用法
Jan 15 Python
python无序链表删除重复项的方法
Jan 17 Python
执行Python程序时模块报错问题
Mar 26 Python
Python BeautifulReport可视化报告代码实例
Apr 13 Python
如何利用python发送邮件
Sep 26 Python
tensorflow+k-means聚类简单实现猫狗图像分类的方法
Apr 28 Python
如何将numpy二维数组中的np.nan值替换为指定的值
May 14 Python
python3读取文件指定行的三种方法
May 24 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
PHP __autoload()方法真的影响性能吗?
2012/03/30 PHP
php全角字符转换为半角函数
2014/02/07 PHP
PHP header()函数常用方法总结
2014/04/11 PHP
CodeIgniter配置之routes.php用法实例分析
2016/01/19 PHP
PHP简单实现数字分页功能示例
2016/08/24 PHP
php设计模式之观察者模式定义与用法经典示例
2019/09/19 PHP
JavaScript 参考教程
2006/12/29 Javascript
Javascript下的keyCode键码值表
2007/04/10 Javascript
Jquery ui css framework
2010/06/28 Javascript
nodeType属性返回被选节点的节点类型介绍
2013/11/22 Javascript
充分发挥Node.js程序性能的一些方法介绍
2015/06/23 Javascript
JavaScript作用域示例详解
2016/07/07 Javascript
JS实现漂亮的时间选择框效果
2016/08/20 Javascript
jQuery Easyui datagrid editor为combobox时指定数据源实例
2016/12/19 Javascript
微信小程序 开发之全局配置
2017/05/05 Javascript
BootStrap 页签切换失效的解决方法
2017/08/17 Javascript
10个在JavaScript开发中常遇到的BUG
2017/12/18 Javascript
webpack-url-loader 解决项目中图片打包路径问题
2019/02/15 Javascript
vue component 中引入less文件报错 Module build failed
2019/04/17 Javascript
JS实现动态星空背景效果
2019/11/01 Javascript
jQuery实现颜色打字机的完整代码
2020/03/19 jQuery
js实现简易计算器小功能
2020/11/18 Javascript
[01:12]快闪回顾DOTA2亚洲邀请赛(DAC) 静候2018新征程开启
2018/03/11 DOTA
有关wxpython pyqt内存占用问题分析
2014/06/09 Python
Python中os.path用法分析
2015/01/15 Python
Python PyAutoGUI模块控制鼠标和键盘实现自动化任务详解
2018/09/04 Python
Python统计分析模块statistics用法示例
2019/09/06 Python
做一个能自适应高度的textarea的示例代码
2019/09/06 HTML / CSS
税务专业毕业生自荐信
2013/11/10 职场文书
文秘求职信范文
2014/04/10 职场文书
商场开业庆典策划方案
2014/06/02 职场文书
食品工程专业求职信
2014/06/15 职场文书
房屋过户委托书范本
2014/10/07 职场文书
党支部承诺书
2015/01/20 职场文书
Django实现drf搜索过滤和排序过滤
2021/06/21 Python
了解MySQL查询语句执行过程(5大组件)
2022/08/14 MySQL