我是靠谱客的博主 花痴睫毛膏,最近开发中收集的这篇文章主要介绍python中并发请求接口的多种实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

多线程和协程

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

优点:

  1. 可以直接把同步代码补丁为异步,更加易用

缺点:

  1. 在Windows上运行得不好
  2. 不能猴补丁C扩展
  3. 需要安装第三方库
  4. 调试基于gevent的代码困难

Asyncio

优点:

  1. 是标准库的一部分,官方支持,很好的维护,很好的社区支持
  2. 方便调试
  3. 代码定义让出cpu资源,实现并发

缺点:

  1. 无法把原有的代码改为异步,需要重写代码
  2. 需要配合异步的第三方库一起使用才能实现相应的异步功能,例如 aiohttp,aiomysql

名词解释:

  1. 事件循环:是每个 asyncio 应用程序的核心,每个协程任务都运行在事件循环中,事件循坏来管理分配不同任务的执行,比如aiohttp 请求后会让出控制权,事件循环就会继续执行下面的任务而不会造成阻塞,达到提高并发的目的。当然不是所有协程任务都可以让出控制权,所有要使用aiohttp而不是requests
  2. 协程 :async关键字声明的特殊函数,就是协程,这时候协程已经不具备函数的特性,所以协程不是函数
  3. 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中并发请求接口的多种实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部