您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Python

实例Python并发编程

时间:2021-04-01 10:22:46  来源:今日头条  作者:虫虫安全

我们知道现在硬件飞速发展,多核CPU 成了标配。为了提高程序的效率,一个方面改变程序的顺序执行,用异步方式,防止由于某个耗时步骤,而影响后续程序的执行。另一个方面是采用并发方式执行,重复利用多核CPU优势加速执行。关于并发编程大家可能比较熟悉的是Golang的协程、通道和Node.js 的async.parallel异步并发编程。就并发编程来说,Python/ target=_blank class=infotextkey>Python不是一门合适的语言,主要是Python有一个解析器(CPython)内置的全局解释锁GIL。 GIL限制Python中一次只能有一个线程访问Python对象,从而我们无法实现多线程分配到多个CPU执行,这是一个极大限制,限制Python并发编程。当然限制归限制,Python标准库中都已经引入了多进程和多线程库,所以Python并发程序相当简单。

本文中,虫虫给大家实例介绍一下Python的并发编程

实例Python并发编程

 

并发编程

关于python并发编程,我们推荐优雅地创建并发程序三部曲:

首先,编写一个按顺序执行任务的脚本。

其次,脚本中的执行程序(耗时任务)提取为一个执行函数,并使用map函数调用。

最后,使用并发模块中的函数替换map即可。

实例脚本

该实例中,我们用到一个小的图片爬虫,使用urllib从Picsum网站下载20张图片,具体脚本程序如下:

import urllib.request
import time
url = 'https://picsum.photos/id/{}/200/300'
args = [(n, url.format(n)) for n in range(20)]
start = time.time()
for pic_id, url in args:
 res = urllib.request.urlopen(url)
 pic = res.read()
 with open(f'./{pic_id}.jpg', 'wb') as f:
 f.write(pic)
 print(f'图片 {pic_id} 已经保存!')
end = time.time()
msg ='共耗时 {:.3f} 秒下载完成。'
print(msg.format(end-start)

python pic_get.py 运行该脚本,结果如下:

图片 0 已经保存!
图片 1 已经保存!
图片 2 已经保存!
...
共耗时 26.694 秒下载完成。

下载共耗费不到半分钟,接着按照我们优雅的三部曲,改造这个脚本。

使用Map改造脚本

下面脚本中,我们将下载图片的代码打包到一个执行函数get_img中。

import urllib.request
import time
def get_img(pic_id, url):
 res = urllib.request.urlopen(url)
 pic = res.read()
 with open(f'test/{pic_id}.jpg', 'wb') as f:
 f.write(pic)
 print(f'图片 {pic_id} 已经保存!')
def main():
 url = 'https://picsum.photos/id/{}/200/300'
 pic_ids = [i for i in range(20)] ;
 urls=[(url.format(n)) for n in range(20)]
 start = time.time()
 for _ in map(get_img, pic_ids, urls):
 pass
 end = time.time()
 msg = '共耗时{:.3f}秒下载完成。'
 print(msg.format(end-start))
if __name__ == '__main__':
 main()

上述脚本中,用map函数替换先前脚本中的for循环(黑体部分)。map是一个函数式编程语法,该函数会生成一个迭代器,迭代器会执行迭代调用get_img()。关于map()函数熟悉函数式编程人可能会觉得有点奇怪,请自己搜索资料充电,此处,我们用它来充当并发编程网关。

图片 0 已经保存!
图片 1 已经保存!
图片 2 已经保存!
...
图片 19 已经保存!
共耗时26.023秒下载完成。

用map改造后,运行脚本总耗时大体上和脚本一致。

多线程并发处理

Python标准库的current.futures模块包含了大量并发编程的包装函数,详细说明,可参见官方文档,此处我们直接上代码。

将pic_get1.py中的程序做简单改进,就能实现多线程脚本:

首先在脚本开头引入多线程函数:

from concurrent.futures import ThreadPoolExecutor

接着替换

 for _ in map(get_img, pic_ids, urls):
 pass

with ThreadPoolExecutor(max_workers=20) as do:
 do.map(get_img, pic_ids, urls)

即可。执行结果:

图片 0 已经保存!
图片 2 已经保存!
图片 5 已经保存!
...
图片 9 已经保存!
共耗时2.913秒下载完成。

总耗时由26秒,减少到了大约3秒。大概快了8倍。并发执行的效果还是杠杠的。

程序中我们使用with ThreadPoolExecutor语句产生一个执行器do。通过将get_img和相应的参数映射到执行程序,自动生成多线程执行。

大家可能注意到了在多线程脚本执行后,图片下载时候不是以前的0~19的顺序的,而是不同线程并发执行的所以完成提示信息也是乱序的。

实例Python并发编程

 

多进程处理

多进程的改造也非常简单,我么只需把之前多线程脚本中的ThreadPoolExecutor替换为ProcessPoolExecutor即可。

from concurrent.futures import ProcessPoolExecutor

...

with ProcessPoolExecutor(max_workers=20) as do:
 do.map(get_img, pic_ids, urls)

执行结果:

图片 9 已经保存!
图片 6 已经保存!
...
图片 11 已经保存!
图片 15 已经保存!
共耗时4.606秒下载完成

也非常快了,4秒钟就完成了,但是比多线程的3秒,稍微慢点。为什么多进程要比多线程慢呢?顾名思义,多进程程序会启用多个进程,而多线程会使用线程。Python中一个进程可以运行多个线程。每个进程都有其适当的Python解释器和适当的GIL。相比较而已,启动一个进程是更加耗时,重的操作,所以需要花费的时间更多。

实例Python并发编程

 

斐波那契数列计算

实例Python并发编程

 

为了进一步说明Python中线程和进程之间的区别,我们再来举一个大量计算的例子,斐波那契数列的计算。

根据斐波那契数列的定义我们用递归方法编写实现其计算:

def fib(n):
 if n == 1:
 return 0
 elif n == 2:
 return 1
 else:
 return fib(n-1) + fib(n-2)

在不使用numpy的情况下用普通Python计算比较慢:

def main():
 fib_range = list(range(1, 35))
 times = []
 for run in range(10):
 start = time.time()
 for n in fib_range:
 fib(n)
 end = time.time()
 times.Append(end-start)
 print('波那契数列fib(35)计算平均耗时 {:.3f}。'.format(np.mean(times))

结果:

波那契数列fib(35)计算平均耗时 5.200

下面我们试着用并发计算来加速计算。

让我们通过线程加速它!为此,我用受信任的ThreadPoolExecutor替换for循环,如下所示:

with ProcessPoolExecutor() as do:
 do.map(fib, fib_range)

执行结果:

波那契数列fib(35)计算平均耗时 5.239。

什么?加速后,反而慢,好像多线程没起到作用。这就是GIL的因素导致的,尽管使用了多个线程,生成了一堆线程,但是这些线程都在同一进程中运行并共享一个GIL。所以斐波那契序列尽管是并发计算的,这些线程在只能在一个CPU上循序执行。

实例Python并发编程

 

进程可以分布在不同的CPU核心,而在同一进程上运行的线程则不能。使CPU消耗最大的操作为CPU绑定操作。为了加快CPU限制的操作,应该启动多个进程计算。我们用ProcessPoolExecutor替换ThreadPoolExecutor再试试:

波那契数列fib(35)计算平均耗时 3.591

性能提高了一点。

除了并发的方式外,我们可以用算法优化方法来提高性能,在数值计算中,这是一种更有效的方法,比如,我们改造fib函数:

def fib(n):
 a, b, i = 0, 1, 1
 while i < n:
 a, b = b, a + b
 i += 1
 return b

上述方法中,巧妙用内存存中的变量历史迭代的前两次结果都存在内存中,所以该次计算中无需回溯迭代计算,这样计算效率O(1),基本上可以秒出结果。

使用新算法后的执行结果:

波那契数列fib(35)计算平均耗时 0.000。

总结

本文我们实例介绍了Python中的并发编程,关于并发编程由于标准库中给我们打包好了方便使用的并发函数使得其使用非常方便。需要注意的是Python中的并发不管是多线程在IO操作中是有效的,而在其他方面,如数值结算时候就受GIL限制无用了。关于并发计算和GIL有心的话,可以参考有关文档进一步深入学习了解。



Tags:并发编程   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
协程(Goroutines)在Go语言中,每一个并发的执行单元叫作一个goroutine。,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang...【详细内容】
2022-06-20  Tags: 并发编程  点击:(27)  评论:(0)  加入收藏
我们知道现在硬件飞速发展,多核CPU 成了标配。为了提高程序的效率,一个方面改变程序的顺序执行,用异步方式,防止由于某个耗时步骤,而影响后续程序的执行。另一个方面是采用并发方...【详细内容】
2021-04-01  Tags: 并发编程  点击:(224)  评论:(0)  加入收藏
上一篇文章 https://fraseryu.github.io/2019/08/25/bing-fa-bian-cheng-zhi-chu-tan/ 给大家带了并发编程的开胃菜,接下来我们逐步上正餐,在吃正餐之前,我还要引用那首诗词:...【详细内容】
2021-03-19  Tags: 并发编程  点击:(309)  评论:(0)  加入收藏
如果你细心观察的话,你会发现,不管是哪一门编程语言,并发类的知识都是在高级篇里。换句话说,这块知识点其实对于程序员来说,是比较进阶的知识。我自己这么多年学习过来,也确实觉得...【详细内容】
2021-03-11  Tags: 并发编程  点击:(276)  评论:(0)  加入收藏
作者公众号:一角钱技术(org_yijiaoqian)前言线程池的具体实现有两种,分别是ThreadPoolExecutor 默认线程池和ScheduledThreadPoolExecutor 定时线程池,上一篇已经分析过ThreadPoo...【详细内容】
2020-12-22  Tags: 并发编程  点击:(179)  评论:(0)  加入收藏
前言现如今,开发程序不仅仅只用单纯的满足用户需求,随着互联网的基本普及,系统能不能承载同时上百万上千万,甚至上亿的访问量,成为了开发设计中必不可少的一个考量环节。例如,过去...【详细内容】
2020-08-12  Tags: 并发编程  点击:(109)  评论:(0)  加入收藏
Disruptor是什么?Disruptor是一个高性能的异步处理框架,一个轻量级的JMS,和JDK中的BlockingQueue有相似处,但是它的处理速度非常快,获得2011年程序框架创新大奖,号称“一个线程一...【详细内容】
2020-07-15  Tags: 并发编程  点击:(124)  评论:(0)  加入收藏
如果我必须选择 Go 的一个伟大特性,那么它必须是内置的并发模型。Go 不仅支持并发性,而且使其更好,更易于使用。Go 并发模型 (goroutine) 对并发编程的作用,就类似于 docker 之...【详细内容】
2020-06-18  Tags: 并发编程  点击:(73)  评论:(0)  加入收藏
有效的提高程序执行效率的两种方法是异步和并发,Golang,node.js之所以可以有很高执行效率主要是他们的协程和异步并发机制。实际上异步和并发是每一种现代语言都在追求的特性,...【详细内容】
2020-06-05  Tags: 并发编程  点击:(94)  评论:(0)  加入收藏
Java并发编程之验证volatile指令重排-理论篇Java并发包下的类中大量使用了volatile关键字。通过之前文章介绍,大家已经知道了volatile的三大特性:共享变量可见性;不保证原子性;...【详细内容】
2020-03-23  Tags: 并发编程  点击:(80)  评论:(0)  加入收藏
▌简易百科推荐
近几年 Web3 被炒得火热,但是大部分人可能还不清楚什么是 Web3,今天就让w3cschool编程狮小师妹带你了解下 Web3 是什么?与我们熟知的 Web1 和 Web2 又有什么区别呢?web3.0什么是...【详细内容】
2022-07-15  编程狮W3Cschool    Tags:Web3.0   点击:(2)  评论:(0)  加入收藏
1、让我们一起来看下吧,直接上图。 第一眼看到是不是觉得很高逼格,暗黑画风,这很大佬。其实它就是------AidLearning。一个运行在安卓平台的linux系统,而且还包含了许多非常强大...【详细内容】
2022-07-15  IT智能化专栏    Tags:AidLearning   点击:(2)  评论:(0)  加入收藏
真正的大师,永远都怀着一颗学徒的心! 一、项目简介 今天说的这个软件是一款基于Python+vue的自动化运维、完全开源的云管理平台。二、实现功能 基于RBAC权限系统 录像回放 ...【详细内容】
2022-07-14  菜鸟程序猿    Tags:Python   点击:(3)  评论:(0)  加入收藏
前言今天笔者想和大家来聊聊python接口自动化的MySQL数据连接,废话不多说咱们直接进入主题吧。 一、什么是 PyMySQL?PyMySQL是在Python3.x版本中用于连接MySQL服务器的一个库,P...【详细内容】
2022-07-11  测试架构师百里    Tags:python   点击:(19)  评论:(0)  加入收藏
aiohttp什么是 aiohttp?一个异步的 HTTP 客户端\服务端框架,基于 asyncio 的异步模块。可用于实现异步爬虫,更快于 requests 的同步爬虫。安装pip install aiohttpaiohttp 和 r...【详细内容】
2022-07-11  VT漫步    Tags:aiohttp   点击:(15)  评论:(0)  加入收藏
今天我们学习下 Queue 的进阶用法。生产者消费者模型在并发编程中,比如爬虫,有的线程负责爬取数据,有的线程负责对爬取到的数据做处理(清洗、分类和入库)。假如他们是直接交互的,...【详细内容】
2022-07-06  VT漫步    Tags:Python Queue   点击:(34)  评论:(0)  加入收藏
继承:是面向对象编程最重要的特性之一,例如,我们每个人都从祖辈和父母那里继承了一些体貌特征,但每个人却又不同于父母,有自己独有的一些特性。在面向对象中被继承的类是父类或基...【详细内容】
2022-07-06  至尊小狸子    Tags:python   点击:(25)  评论:(0)  加入收藏
点击上方头像关注我,每周上午 09:00准时推送,每月不定期赠送技术书籍。本文1553字,阅读约需4分钟 Hi,大家好,我是CoCo。在上一篇Python自动化测试系列文章:Python自动化测试之P...【详细内容】
2022-07-05  CoCo的软件测试小栈    Tags:Python   点击:(27)  评论:(0)  加入收藏
第一种方式:res = requests.get(url, params=data, headers = headers)第二种方式:res = requests.get(url, data=data, headers = headers)注意:1.url格式入参只支持第一种方...【详细内容】
2022-07-05  独钓寒江雪之IT    Tags:Python request   点击:(19)  评论:(0)  加入收藏
什么是python类的多态python的多态,可以为不同的类实例,或者说不同的数据处理方式,提供统一的接口。用比喻的方式理解python类的多态比如,同一个苹果(统一的接口)在孩子的眼里(类实...【详细内容】
2022-07-04  写小说的程序员    Tags:python类   点击:(28)  评论:(0)  加入收藏
站内最新
站内热门
站内头条