Python中的异步与多进程

来源:互联网 发布:usb linux启动盘制作 编辑:程序博客网 时间:2024/05/11 16:19

Python自3.5以来,引入了asyncawait关键字,更好支持了异步IO的操作。本文以下载LFPW数据集为例,对比多进程和异步IO。

LFPW

LFPW was used to evaluate a face part (facial fiducial point) detection method which was trained on 1,132 images and tested on 300 images, and so the data set is divided into two files, one for training and testing.

LFPW是人脸特征点检测的一个数据集。和别的数据集不一样的是,这个数据集没有给出图片,仅仅给出了图片的url。该数据集共给出了1132张训练集和300张测试集数据,由于很多图片url失效,所以1132张图片并不能完全下载下来。这些图片的url保存在一个csv文件中,需要从csv读取到url后进行下载。csv的读取使用了ReadCSV模块,url解析使用了URLParser模块。

多进程

多进程下载使用进程池进行下载。首先引入multiprocessing模块,使用Pool创建进程池,随后将任务加入进程池就完成了。

def main_downloader(csv_path,save_image_path):    url_list=readcsv(csv_path)    p=Pool(4)    for image_url in url_list:        p.apply_async(download_one_url,args=(image_url,save_image_path))    p.close()    p.join()    print("Everything is OK")

p=Pool(4)创建一个大小为4的进程池,使用p.apply_async()将任务加入进程池,对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的任务了。

异步

Python3.5中实现异步操作的是async和await关键字,Python官方文档对此有详细解释 。在官方文档中可以看到async with和async for的用法介绍。其中async with叫做异步上下文管理器(asynchronous context manager);async for叫做异步迭代器(asynchronous-iterators)。两者的用法可以参考我的另一篇博文async with和async for。
由于这里要用到http请求,还需要引入另一个异步http模块模块aiohttp,这样才能创建异步的http请求。关于aiohttp的更多知识,可以阅读官方文档。核心代码如下:

async with aiohttp.ClientSession() as session:    async with session.get(image_url,headers=header) as resp:        try:            assert resp.status == 200,"Failure "+str(resp.status)            await self.save_image(save_image_full_path,await resp.read())            print("success")        except Exception as e:            print(e)

可以看到第一行代码async with aiohttp.ClientSession() as session:就是一个异步上下文管理器,这是aiohttp的推荐写法。
await self.save_image(save_image_full_path,await resp.read())这行表示异步执行save_image函数。save_image函数本身需要使用async修饰,这样这个函数就能够异步执行了,在需要的地方,要使用await与之配合使用才能实现异步操作。
完整代码

from urllib.parse import urlparseimport timeimport sysimport csvimport aiohttpimport asyncioclass LbfwDownloader:    def __init__(self,loop,csv_path,save_image_path):        self.loop=loop        self.csv_path=csv_path        self.save_image_path=save_image_path    def __del__(self):        pass    def read_csv(self,csv_path):        url_collection=[]        with open(csv_path,"r") as csvfile:            lines=csv.reader(csvfile,delimiter="\t")            for line in lines:                url_collection.append(line[0])        return set(url_collection[2:])    async def download_and_save(self,image_url):        image_name=self.get_image_name(image_url)        await self.image_download(image_url,self.save_image_path+image_name)        print(image_name+" is done")        sys.stdout.flush()    def get_image_name(self,url_str):        url=urlparse(url_str)        path=url[2]        image_name=path.split("/")[-1]        return image_name    async def image_download(self,image_url,save_image_full_path):        header={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"}        async with aiohttp.ClientSession() as session:            async with session.get(image_url,headers=header) as resp:                try:                    assert resp.status == 200,"Failure "+str(resp.status)                    await self.save_image(save_image_full_path,await resp.read())                    print("success")                except Exception as e:                    print(e)    async def save_image(self,save_image_full_path,content):        with open(save_image_full_path,"wb") as f:            f.write(content)    def start_download(self):        url_list=self.read_csv(self.csv_path)        tasks = [self.download_and_save(u) for u in url_list]        loop.run_until_complete(asyncio.wait(tasks))        print("Everything is OK")if __name__=="__main__":    t=time.time()    loop = asyncio.get_event_loop()    train=LbfwDownloader(loop,"./url/kbvt_lfpw_v1_train.csv","./images/train/")    train.start_download()    print("time: "+str(time.time()-t))

结果

1132张图片的下载速度如下:
异步:324s
多进程:1305s

造成这样差异的主要原因在于多进程下载时,程序花了很长时间在等待http响应,虽然效率比单个函数依次执行提高了很多,但是由于IO是阻塞的,并不能显著提高下载IO效率。而异步则不然,IO是非阻塞的,程序仅仅会在http响应到达的时候进行处理,IO效率显著提高。

代码

代码存放在GitHub中,欢迎指导:

https://github.com/flyingzhao/LFPWDownloader

0 0
原创粉丝点击