Asynocio异步编程理论篇

Asynocio异步编程理论篇,第1张

Asynocio异步编程理论篇(1) 什么是同步和异步

在正式进入学习前需要弄清楚三组概念:

  • 同步和异步
  • 串行和并行
  • 并行和并发
同步和异步
  • 同步:做一件事情一定要看着它、把它做完,然后再开始下一件事。
  • 异步:做一件事情可以把它交给别人做(不会一直等着它),继续做下一件事。
串行和并行
  • 串行:一件事一件事的做。
  • 并行:两件事同时做,即同一个时间点有多个人在干活。
并行和并发
  • 并行:同一时间点有多个任务在执行。
  • 并发:一段时间内,有多个任务轮流执行;且同一个时间点依然只执行一个任务。

通常大家说的高并发指的就是【并发】这个概念,充分压榨 CPU 资源。

Python 中实现并发的手段有三个:进程、线程、协程。

什么是协程
  • CPU 在执行任务时,整整干活的是线程,当一个线程遇到 IO 时会交出执行权,切换到另个一个线程。

  • 协程 Coroutine,也称为微线程,是一种用户态内的上下文切换技术,即在一个线程内切换被执行的代码块。代码级别的切换。

示例:同步执行的代码,代码块依次执行

def f1():
    print(1)
    print(2)


def f2():
    print(3)
    print(4)


f1()
f2()

# 依次打印:1 2 3 4

示例:使用 yield 实现的协程,实现代码块的切换执行。

def f1():
    yield 1
    yield from f2()
    yield 2


def f2():
    yield 3
    yield 4


generator = f1()  # f1()是生成器

for i in generator:
    print(i)   # 依次打印:1 3 4 2
第三方包实现协程

示例:使用第三方包 greenlet 实现协程

from greenlet import greenlet

def f1():
    print(1)
    gr2.switch()
    print(2)
    gr2.switch()


def f2():
    print(3)
    gr1.switch()
    print(4)


gr1 = greenlet(f1)
gr2 = greenlet(f2)

gr1.switch() # 依次打印:1 3 2 4

示例:使用第三方包 gevent 实现协程(内部基于 greenlet),遇到 IO 会自动切换。

import gevent


def f1():
    print(1)
    gevent.sleep(2)  # 注意不使用time.sleep(2)
    print(2)


def f2():
    print(3)
    gevent.sleep(2)
    print(4)


g1 = gevent.spawn(f1)
g2 = gevent.spawn(f2)


gevent.joinall([g1, g2]) # 等待所有函数运行结束,依次打印1 3 -睡2s- 2 4

gevent 基于 greenlet
猴子补丁:from gevent import monkey; monkey.patch_all()

asyncio 实现协程

Python3.4 之前官方未提供协程的类库,一般使用 gevent 实现协程达到异步编程的目的。

Python3.4 之后官方推出 asyncio 模块,有如下几个特点:

  • 单线程
  • 事件循环
  • 适用于 IO 密集型场景

示例:通过 asyncio 实现协程

import asyncio


@asyncio.coroutine
def f1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO睡2s
    print(2)


@asyncio.coroutine
def f2():
    print(3)
    yield from asyncio.sleep(2)  # 遇到IO睡2s
    print(4)


tasks = [
    asyncio.ensure_future(f1()),
    asyncio.ensure_future(f2())
]

loop = asyncio.get_event_loop()     # 得到一个事件循环
loop.run_until_complete(asyncio.wait(tasks)) # 等待所有任务执行完毕
# 打印结果:1 3 -睡2s- 2 4

注意

  • asyncio 内部是基于 yield 实现的协程,本质上在事件循环内切换执行任务。
  • @asyncio.coroutine 这个装饰器在 Python3.8 中将会被弃用,推荐使用 async def
async/await实现协程
  • Python3.5 官方推出 asyncawait 关键字,目的是让协程代码可以更加简洁。
import asyncio


async def f1():
    print(1)
    await asyncio.sleep(2)  # 等 可以等待的对象
    print(2)


async def f2():
    print(3)
    await asyncio.sleep(2)
    print(4)


tasks = [
    asyncio.ensure_future(f1()),
    asyncio.ensure_future(f2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# 打印结果:1 3 -睡2s- 2 4

注意

  • await 只能用在 async def 内,但在 ipython 中可以直接使用
体验 async 异步爬图片
  • 协程的主要用途在 IO 密集型场景,本节通过爬虫带着大家体验下异步爬虫的高效。

示例:同步爬虫

import requests


def download_img(url):
    file_name = url.rsplit('/')[-1]
    print(f"下载图片:{file_name}")
    response = requests.get(url)
    with open(file_name, mode='wb') as file:
        file.write(response.content)
    print(f"下载完成:{file_name}")


def main():
    urls = [
        "https://tenfei05.cfp.cn/creative/vcg/800/new/VCG41560336195.jpg",
        "https://tenfei03.cfp.cn/creative/vcg/800/new/VCG41688057449.jpg",
        ]
    for item in urls:
        download_img(item)


main()

# 遍历下载列表,第一张图片下载结束,再开始下载下一张图片。

示例:使用 asyncio 异步下载图片

import asyncio
import aiohttp


async def download_img(session, url):
    file_name = url.rsplit('/')[-1]
    print(f"下载图片:{file_name}")
    response = await session.get(url, ssl=False)
    content = await response.content.read()
    with open(file_name, mode='wb') as file:
        file.write(content)
    print(f"下载完成:{file_name}")


async def main():
    urls = [
        "https://tenfei05.cfp.cn/creative/vcg/800/new/VCG41560336195.jpg",
        "https://tenfei03.cfp.cn/creative/vcg/800/new/VCG41688057449.jpg",
    ]
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.ensure_future(download_img(session, url)) for url in urls]
        await asyncio.wait(tasks)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

总结

  • IO 密集型场景,使用协程优势明显。IO 等待时切换执行其他任务,进而提高效率。
  • 基于 async & await 关键字的协程可以实现异步编程,这也是目前的主流方式。
事件循环
  • 事件循环是 asyncio 的核心。在 Python 异步应用中,每一个异步任务都是在事件循环中被执行的。
  • 简单理解,可以把事件循环当成一个 while 循环,循环内部执行任务,当一个任务遇到 IO 挂起时,就会立即切换执行另一个任务,如此反复,在特定条件下会终止循环。

官网:https://docs.python.org/3/library/asyncio-eventloop.html

  • 图解事件循环:

注意:程序遇到await关键词时,程序会挂起,事件循环会再找一个任务开始执行。

  • 创建事件循环
import asyncio
asyncio.get_event_loop()
  • 推荐使用 asyncio.run() 来运行事件循环
import asyncio


async def f1():
    await asyncio.sleep(5)

# loop = asyncio.get_event_loop()
# loop.run_until_complete(f1())
# run等价于上面两行代码
asyncio.run(f1())

协程函数和协程对象
  • 协程函数:通过 async def 定义的函数
  • 协程对象:协程函数执行后返回的对象

注意:

  • 调用协程函数,函数内部代码不会执行,只是会返回一个协程对象。
  • 想要执行协程函数内部代码,需要配合事件循环。
import asyncio


async def f1():
    await asyncio.sleep(5)

# loop = asyncio.get_event_loop() # 获取一个事件循环
# loop.run_until_complete(f1())  # 将协程函数当做任务添加到事件循环中
# run等价于上面两行代码
asyncio.run(f1())

注意:

  • run 会新建一个事件循环,任务结束时会自动关闭它。
  • run 应该作为异步程序的唯一入口,只能被调用一次。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/870318.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存