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进行进程间通信,分配协程任务,注册到不同进程的事件循环中,实现并发请求。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值