概述
多线程和协程
1.多线程
python的多线程由于全局锁并不能并行,而是是单线程执行的分时复用模式。
- 线程A占用CPU,获得GIL锁。
- 遇到IO操作,中断,则释放锁。
- 没遇到IO,则执行1000字节指令(py2)或者执行运行时间15ms,释放。
- 根据竞态原则,抢到的线程会占用cpu,从新获得GIL。
每个线程可能会由各种计算和IO操作组成,再整个执行过程中,cpu会因为上述原因不断地在各个线程之间切换,使得线程在执行IO的时候,cpu不会空等它结束而是去服务其他线程,这样就造成了多线程像是并行一样的效果,但上下文切换开销较大。
2.协程
本质是单线程,是在单线程上运行的多个子程序,通过代码yield实现让出阻塞资源,实现高并发,因为是在同一个线程里线程的切换开销很小。
在python2.7版本使用gevent来实现协程,现在协程也支持python3的版本,但python3中官方引入了新的协程实现方案asyncio,社区更加强大,缺点是要重写协程代码
Gevnet和Asyncio
gevent
优点:
- 可以直接把同步代码补丁为异步,更加易用
缺点:
- 在Windows上运行得不好
- 不能猴补丁C扩展
- 需要安装第三方库
- 调试基于gevent的代码困难
Asyncio
优点:
- 是标准库的一部分,官方支持,很好的维护,很好的社区支持
- 方便调试
- 代码定义让出cpu资源,实现并发
缺点:
- 无法把原有的代码改为异步,需要重写代码
- 需要配合异步的第三方库一起使用才能实现相应的异步功能,例如 aiohttp,aiomysql
名词解释:
- 事件循环:是每个 asyncio 应用程序的核心,每个协程任务都运行在事件循环中,事件循坏来管理分配不同任务的执行,比如aiohttp 请求后会让出控制权,事件循环就会继续执行下面的任务而不会造成阻塞,达到提高并发的目的。当然不是所有协程任务都可以让出控制权,所有要使用aiohttp而不是requests
- 协程 :async关键字声明的特殊函数,就是协程,这时候协程已经不具备函数的特性,所以协程不是函数
- futures:调度的协程被包装在Tasks 中,它是一种Future类型,loop.create_task() 或 asyncio.ensure_future(),futures对象是具有状态 (Pending,Running,Done,Cancelled)
举个例子:
import time
import asyncio
start = time.time()
def tic():
return 'at %1.1f seconds' % (time.time() - start)
async def gr1():
# Busy waits for a second, but we don't want to stick around...
print('gr1 started work: {}'.format(tic()))
await asyncio.sleep(2)
print('gr1 ended work: {}'.format(tic()))
return "gr1"
async def gr2():
# Busy waits for a second, but we don't want to stick around...
print('gr2 started work: {}'.format(tic()))
await asyncio.sleep(2)
print('gr2 Ended work: {}'.format(tic()))
return "gr2"
async def gr3():
print('gr3 started work: {}'.format(tic()))
await asyncio.sleep(1)
print('gr3 Ended work: {}'.format(tic()))
return "gr3"
def run(main_func=None):
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
asyncio.get_event_loop().run_until_complete(main_func)
finally:
try:
if hasattr(loop, "shutdown_asyncgens"):
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
asyncio.set_event_loop(None)
loop.close()
async def main():
tasks = [gr1(), gr2(), gr3()]
done, pending = await asyncio.wait(tasks)
print([res.result() for res in done])
if __name__ == '__main__':
run(main())
"""
gr2 started work: at 0.0 seconds
gr3 started work: at 0.0 seconds
gr1 started work: at 0.0 seconds
gr3 Ended work: at 1.0 seconds
gr2 Ended work: at 2.0 seconds
gr1 ended work: at 2.0 seconds
['gr1', 'gr2', 'gr3']
"""
- 首先,我们声明了几个简单的协程,它们假装使用asyncio 中的sleep函数进行非阻塞工作。
- 然后通过asyncio.wait 注册到事件循环中
- 最后 run 方法负责创建事件循环和调度我们的协程。
请注意,这里协程任务注册到事件循环中没有loop.create_task() 或 asyncio.ensure_future(),因为asyncio.wait里面做了创建future操作
通过await一个协程上使用,可以将控制权交还给事件循环,在这里的sleep情况,协程将产生和事件循环将切换上下文来调度执行下一个任务,因为协程是在一个线程中,上下文切换的速度是非常快速的。
尽管执行的顺序不一样但是返回的结果是有序的,因为结果是通过插入的方式插入到结果列表中
多进程+多线程和多进程+协程
如果单单使用多进程来实现并发请求,相比多线程,协程来说多太过笨重,上下文切换开销更大
所以采取多进程+多线程和多进程+协程是更优的办法。
多进程+多线程
多核cpu中,每个进程占用一个cpu(如果进程数大于cpu核数还是要进行进程间的切换),每个进程下有多个进程,和一个GIL,相当每个进程都是独立的GIL,理论上的速度提升 *进程数 的倍数。但是进程间的切换开销还是存在,所以更推荐多进程+协程的方案。
多进程+协程
多进程中每个进程都有一个线程,叫为主线程,在每个主线程中实现协程的事件循环,queue进行进程间通信,分配协程任务,注册到不同进程的事件循环中,实现并发请求。
最后
以上就是花痴睫毛膏为你收集整理的python中并发请求接口的多种实现的全部内容,希望文章能够帮你解决python中并发请求接口的多种实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复