装饰器实现多线程多协程并发编程
有时候写一些规模不算大得小脚本的时候,特意去实现多线程和协程的编程是一件繁杂的事情,在轻量级的爬虫中我们希望用同步编程的思想来实现异步的执行,下面给出一个初步的解决方案,通过装饰器装饰函数使请求函数可以接受一个可迭代的链接容器。转载请注明出处。
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-08-14 20:52:16 # @Author : Sheldon (thisisscret@qq.com) # @blogs : 谢耳朵的派森笔记 # @Link : https://www.cnblogs.com/shld/
from gevent.pool import Group from gevent import monkey monkey.patch_all() from concurrent.futures import ThreadPoolExecutor from functools import partial, wraps from multiprocessing import cpu_count def eqlike_split(src, num): len_ = len(src) div = len_ // num mod = len_ % num sdx = 0 splist = [] for p_len in ([1] * mod + [0] * (num - mod)): edx = p_len + div + sdx splist.append(src[sdx:edx]) sdx = edx return splist def funcsplit(func, *args, **kw): """将args的首个元素分离出来,剩下的固定赋与为func函数的参数 生成新的默认参数的函数,方便后续函数传参""" seed = args[0] new_args = args[1:] myfunc = partial(func, *new_args, **kw) return myfunc, seed def aioreq(func, urlist, aionum=None, **kw): """gevent异步协程池执行func,返回response生成器""" igroup = Group() return igroup.imap_unordered(func, urlist, maxsize=aionum) def thread_(func, seed, mtnum=2, **kw): """线程池池执行func,返回response生成器""" with ThreadPoolExecutor(mtnum) as executor: return executor.map(func, seed) def mtaio(func, seed, mtnum=None, aionum=None, **kw): """多线程协程并发执行func,返回两层深度的response生成器""" spnum = mtnum if not mtnum: spnum = cpu_count() new_seed = eqlike_split(seed, spnum) myaioreq = partial(aioreq, func, aionum=aionum) return thread_(myaioreq, new_seed, mtnum) def func2dec(decfunc=mtaio, mtnum=None, aionum=None): """使函数第一个参数接受变为参数迭代器的装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kw): fstuple = funcsplit(func, *args, **kw) return decfunc(*fstuple, mtnum=mtnum, aionum=aionum) return wrapper return decorator
用法:func2dec(decfunc=mtaio,mtnum=None,aionum=None)作为装饰器装饰请求函数就可以了,decfunc可选多线程、协程、多线程协程;mtnum使线程池大小,默认cpu个数;aionum是协程池大小,默认所有任务。