本文系作者「无名小妖」的第二篇原创投稿文章,作者通过用爬虫示例来说明并发相关的多线程、多进程、协程之间的执行效率对比。如果你喜欢写博客,想投稿可微信我,有稿费酬劳。
假设我们现在要在网上下载图片,一个简单的方法是用 requests+BeautifulSoup。注:本文所有例子都使用python3.5)
示例 1:get_photos.py
import os
import time
import uuid
import requests
from bs4 import BeautifulSoup
def out_wrapper(func): # 记录程序执行时间的简单装饰器
def inner_wrapper():
start_time = time.time()
func()
stop_time = time.time()
print('Used time {}'.format(stop_time-start_time))
return inner_wrapper
def save_flag(img, filename): # 保存图片
path = os.path.join('down_photos', filename)
with open(path, 'wb') as fp:
fp.write(img)
def download_one(url): # 下载一个图片
image = requests.get(url)
save_flag(image.content, str(uuid.uuid4()))
def user_conf(): # 返回30个图片的url
url = 'https://unsplash.com/'
ret = requests.get(url)
soup = BeautifulSoup(ret.text, "lxml")
zzr = soup.find_all('img')
ret = []
num = 0
for item in zzr:
if item.get("src").endswith('80') and num < 30:
num += 1
ret.append(item.get("src"))
return ret
@out_wrapper
def download_many():
zzr = user_conf()
for item in zzr:
download_one(item)
if __name__ == '__main__':
download_many()
示例1进行的是顺序下载,下载30张图片的平均时间在60s左右(结果因实验环境不同而不同)。
这个代码能用但并不高效,怎么才能提高效率呢?
参考开篇的示意图,有三种方式:多进程、多线程和协程。下面我们一一说明:
我们都知道 Python 中存在 GIL(主要是Cpython),但 GIL 并不影响 IO 密集型任务,因此对于 IO 密集型任务而言,多线程更加适合(线程可以开100个,1000个而进程同时运行的数量受 CPU 核数的限制,开多了也没用)
不过,这并不妨碍我们通过实验来了解多进程。
示例2
from multiprocessing import Process
from get_photos import out_wrapper, download_one, user_conf
@out_wrapper
def download_many():
zzr = user_conf()
task_list = []
for item in zzr:
t = Process(target=download_one, args=(item,))
t.start()
task_list.append(t)
[t.join() for t in task_list] # 等待进程全部执行完毕(为了记录时间)
if __name__ == '__main__':
download_many()
本示例重用了示例1的部分代码,我们只需关注使用多进程的这部分。
笔者测试了3次(使用的机器是双核超线程,即同时只能有4个下载任务在进行),输出分别是:19.5s、17.4s和18.6s。速度提升并不是很多,也证明了多进程不适合io密集型任务。
还有一种使用多进程的方法,那就是内置模块futures中的ProcessPoolExecutor。
示例3
from concurrent import futures
from get_photos import out_wrapper, download_one, user_conf
@out_wrapper
def download_many():
zzr = user_conf()
with futures.ProcessPoolExecutor(len(zzr)) as executor:
res = executor.map(download_one, zzr)
return len(list(res))
if __name__ == '__main__':
download_many()
使用 ProcessPoolExecutor 代码简洁了不少,executor.map 和标准库中的 map用法类似。耗时和示例2相差无几。多进程就到这里,下面来体验一下多线程。
示例4
import threading
from get_photos import out_wrapper, download_one, user_conf
@out_wrapper
def download_many():
zzr = user_conf()
task_list = []
for item in zzr:
t = threading.Thread(target=download_one, args=(item,))
t.start()
task_list.append(t)
[t.join() for t in task_list]
if __name__ == '__main__':
download_many()
threading 和 multiprocessing 的语法基本一样,但是速度在9s左右,相较多进程提升了1倍。
下面的示例5和示例6中分别使用内置模块 futures.ThreadPoolExecutor 中的 map 和submit、as_completed
示例5
from concurrent import futures
from get_photos import out_wrapper, download_one, user_conf
@out_wrapper
def download_many():
zzr = user_conf()
with futures.ThreadPoolExecutor(len(zzr)) as executor:
res = executor.map(download_one, zzr)
return len(list(res))
if __name__ == '__main__':
download_many()
示例6:
Executor.map 由于和内置的map用法相似所以更易于使用,它有个特性:返回结果的顺序与调用开始的顺序一致。不过,通常更可取的方式是,不管提交的顺序,只要有结果就获取。
为此,要把 Executor.submit 和 futures.as_completed结合起来使用。
最后到了协程,这里分别介绍 gevent 和 asyncio。
示例7
示例8
import uuid
import asyncio
import aiohttp
from get_photos import out_wrapper, user_conf, save_flag
async def download_one(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
save_flag(await resp.read(), str(uuid.uuid4()))
@out_wrapper
def download_many():
urls = user_conf()
loop = asyncio.get_event_loop()
to_do = [download_one(url) for url in urls]
wait_coro = asyncio.wait(to_do)
res, _ = loop.run_until_complete(wait_coro)
loop.close()
return len(res)
if __name__ == '__main__':
download_many()
协程的耗时和多线程相差不多,区别在于协程是单线程。具体原理限于篇幅这里就不赘述了。
但是我们不得不说一下asyncio,asyncio是Python3.4加入标准库的,在3.5为其添加async和await关键字。或许对于上述多线程多进程的例子你稍加研习就能掌握,但是想要理解asyncio你不得不付出更多的时间和精力。
另外,使用线程写程序比较困难,因为调度程序任何时候都能中断线程。必须保留锁以保护程序,防止多步操作在执行的过程中中断,防止数据处于无效状态。
而协程默认会做好全方位保护,我们必须显式产出才能让程序的余下部分运行。对协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任意时刻只有一个协程运行。想交出控制权时,可以使用 yield 或 yield from(await) 把控制权交还调度程序。
总结
本篇文章主要是将python中并发相关的模块进行基本用法的介绍,全做抛砖引玉。而这背后相关的进程、线程、协程、阻塞io、非阻塞io、同步io、异步io、事件驱动等概念和asyncio的用法并未介绍。大家感兴趣的话可以自行google或者百度,也可以在下方留言,大家一起探讨。
相比于R5 2400G平台直接搭配原装风扇凑合用,作为游戏主力机的i7-8700平台还是搭配好一点的散热器,不管是水冷还是风冷,只要能够稳压8700。相比于立柱式的风冷散热器,水冷散热器的优势就是不用担心水冷头的高度问题,避免了盖不上侧盖的问题。对于水冷的选购上,是120冷排还是240冷排,是支持双平台还是单平台,是否选购RGB风扇,主要看个人爱好,目前主流中塔机箱在安装并不会有什么问题。下面来...
3Web Dynpro ABAP DevelopmentBasic: Web Dynpro application组成结构;Cross-Component Programming:跨组件通信;Dynamic Programming:上下文,布局动态操作;Integration:不同应用技术,程序集成;Advanced Concepts: Web Dynpro ABAP高级项目中可能具有..._webdynpro for abap开发教程
视频播放---jiecaovideoplayer的使用原创 2017年02月26日 00:14:39首先是github地址https://github.com/lipangit/JieCaoVideoPlayer/下面这个是带中文说明的https://github.com/lipangit/JieCaoVideoPlayer/blob/develop/R
uniapp项目打包成H5的方法_hbuilder打包h5
网上查了好多,不过感觉都不能直接拿来用。个人就直接对数据排序然后输出,top函数不知道怎么回事用不来,直接用limit限制输出个数来假装top了select col from tb group by colorder by count(*) desclimit k..._怎么用hive求浏览量最多的十个地区
傅里叶变换(Fourier Transform)空间、基,内积与投影此处各种概念来自《Linear Algebra Done Right》R3R^3R3中的一组基:e1=[1,0,0]T,e2=[0,1,0]T,e3=[0,0,1]Te_1 = [1, 0, 0]^T, e_2 = [0, 1, 0]^T, e_3 = [0, 0, 1]^Te1=[1,0,0]T,e2=[0,1,0]T,e3=[0,0,1]T对任意一个向量,比如[2,3,4]T[2, 3, 4]^T[2,3,4]T_矩阵的 laplacian 变换
一、简介架构模式是对给定上下文的软件架构中常见问题的一种通用的可复用的解决方案。一种模式就是特定上下文的问题的一种解决方案。大体上,主要有下面这7种架构模式:分层架构多层架构管道/过滤器架构客户端/服务器架构模型/视图/控制器架构事件驱动架构微服务架构二、分层架构最常见的架构模式就是分层架构或者称为 n 层架构。大部分软件架构师、设计师和开发者都对这个架构模式非常熟悉。尽管对于层的数量和类型没有具体限制,但大部分分层架构主要由四层组成:展现层、业务层、持久层和数据库层。如下图所示_架构模式
文章目录前言第一节、配置文件优先级第二节、配置方式1. 多profile文件2. yml多文档第三节、激活方式1. 配置文件(前面一直在用的)2. 虚拟机参数VM options3. 命令行参数(program arguments)第四节、运行时指定配置前言profile用于多环境的激活和配置,用来切换生产,测试,本地等多套不通环境的配置。如果每次去更改配置就非常麻烦,profile就是用来切换多环境配置的。第一节、配置文件优先级appliacation.properties>appliaca_application-prod
监听并捕获到长久按住屏幕的手势动作创建一个UILongPressGestureRecognizer的实例对象,并把这个对象绑定在你的视图控制器里面。numberOfTapsRequired 这个属性保存了用户的点击次数,当这个手势动作触发之前,并没有一个手势是在屏幕上的,当第一次把手指点击在屏幕上,然后拿开,这个动作可以称为一次点击事件。numberOfTouchesRe_ios 手势监听
df -h发现这个/dev/mapper/cl-root 占用了 100%全局查找大文件find / -xdev -size +100M -exec ls -l {} \发现这个服务器上,有个备份文件夹占用了很多批量删除指定类型的文件# find . -name "*.java" |xargs rm -rfv问题解决!...
在windows下有两种安装方式,一种是msi的向导式安装,另一种是压缩包解压后手动式安装,本文就后一种方式提供安装帮助,是基本翻译https://dev.mysql.com/doc/refman/8.0/en/windows-install-archive.html,如有错误,请指正。压缩包安装也称绿色安装,整个安装过程涉及9个步骤:(特别注明:在安装过程从出现问题请见最后的 安装问题汇集)1)..._mysql用户几个人登录
手上有个微信小程序项目,因为对Python相对熟悉一些,打算后端用python写,具体采用python 轻量级的flask框架。在做的过程中,有些问题需要考虑,记录在下边.1. 开发的小程序后端怎么区分不同的小程序用户?或者说有哪些属性可以唯一的标识一个用户呢?首先想到的是 微信号,手机号,微信号好像现在还没有API可以获取,手机号又比较麻烦还涉及到读取用户信息,进一步的做法是用openid, u..._微信小程序 pythonflask 调用接口