详解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中的序列化存储的方法
Apr 28 Python
tensorflow实现softma识别MNIST
Mar 12 Python
python实现内存监控系统
Mar 07 Python
Python并发之多进程的方法实例代码
Aug 15 Python
python的concat等多种用法详解
Nov 28 Python
Flask模板引擎之Jinja2语法介绍
Jun 26 Python
在Django model中设置多个字段联合唯一约束的实例
Jul 17 Python
django搭建项目配置环境和创建表过程详解
Jul 22 Python
wxpython自定义下拉列表框过程图解
Feb 14 Python
Linux系统下升级pip的完整步骤
Jan 31 Python
Python 用户输入和while循环的操作
May 23 Python
Flask搭建一个API服务器的步骤
May 28 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
DC宇宙的第一个英雄,堪称动漫史鼻祖,如今成为美国文化的象征
2020/04/09 欧美动漫
PHP显示今天、今月、上月、今年的起点/终点时间戳的代码
2011/05/25 PHP
基于empty函数的输出详解
2013/06/17 PHP
PHP使用pear自带的mail类库发邮件的方法
2015/07/08 PHP
PHP数组实际占用内存大小原理解析
2020/12/11 PHP
JS等比例缩小图片尺寸的实例
2013/02/27 Javascript
JavaScript将字符串转换成字符编码列表的方法
2015/03/19 Javascript
解决jQuery uploadify在非IE核心浏览器下无法上传
2015/08/05 Javascript
jQuery Mobile中的button按钮组件基础使用教程
2016/05/23 Javascript
jQuery实现智能判断固定导航条或侧边栏的方法
2016/09/04 Javascript
AngularJs基于角色的前端访问控制的实现
2016/11/07 Javascript
关于Promise 异步编程的实例讲解
2017/09/01 Javascript
微信小程序实现导航栏选项卡效果
2020/06/19 Javascript
NodeJS实现不可逆加密与密码密文保存的方法
2018/03/16 NodeJs
Vue.js实现双向数据绑定方法(表单自动赋值、表单自动取值)
2018/08/27 Javascript
vuex 中插件的编写案例解析
2019/06/10 Javascript
vue实现浏览器全屏展示功能
2019/11/27 Javascript
微信小程序点击item使之滚动到屏幕中间位置
2020/03/25 Javascript
vue实现商品列表的添加删除实例讲解
2020/05/14 Javascript
jQuery 动画与停止动画效果实例详解
2020/05/19 jQuery
解决removeEventListener 无法清除监听的问题
2020/10/30 Javascript
[36:22]VP vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
Python Tkinter GUI编程入门介绍
2015/03/10 Python
学习python之编写简单简单连接数据库并执行查询操作
2016/02/27 Python
python GUI库图形界面开发之PyQt5下拉列表框控件QComboBox详细使用方法与实例
2020/02/27 Python
耐克美国官网:Nike.com
2016/08/01 全球购物
荷兰在线钓鱼商店:Raven
2019/06/26 全球购物
业务员岗位职责范本
2013/12/15 职场文书
初三开学计划书
2014/04/27 职场文书
文秘专业应届生求职信
2014/05/26 职场文书
信息管理专业自荐书
2014/06/05 职场文书
民间借贷纠纷答辩状
2015/08/03 职场文书
三好学生主要事迹材料
2015/11/03 职场文书
2016教师校本培训心得体会
2016/01/08 职场文书
go语言中GOPATH GOROOT的作用和设置方式
2021/05/05 Golang
python绘制云雨图raincloud plot
2022/08/05 Python