python协程,asyncio,异步编程

发布时间:2023年12月28日

为什么要用协程,异步?

  • 异步非阻塞、asyncio

  • tornado、fastapi、djando 3.x asgi 、aiohttp 都在异步 ->提升性能

  • 使用协程和异步编程有以下几个主要原因:

    1. 提高性能:传统的同步阻塞方式在处理IO操作时,线程会被阻塞,导致CPU资源无法充分利用。而协程和异步编程可以在进行IO操作时,将线程释放出来处理其他任务,从而充分利用CPU资源,提高程序的整体性能。
    2. 增加并发能力:协程和异步编程可以实现并发执行多个任务,而不需要创建大量的线程。线程的创建和切换是比较消耗资源的操作,而协程的切换只需要保存上下文状态,开销较小,可以同时执行大量的任务。
    3. 简化编程模型:通过使用协程和异步编程,可以简化复杂的多线程编程模型。传统的多线程编程需要考虑线程之间的同步和通信问题,而协程和异步编程可以通过事件循环和回调函数的方式实现简洁的编程模型,使代码更易读、易写、易于维护。
    4. 提高系统的可扩展性:协程和异步编程可以实现高效的资源利用,减少了资源的浪费,因此能够更好地应对高并发的场景。通过合理地利用协程和异步编程,可以提高系统的可扩展性,使系统能够处理更多的请求。

协程能做什么?

协程是一种轻量级的并发编程方式,可以用于处理各种任务。以下是协程的一些常见应用场景:

  1. 异步IO操作:协程可以用于处理异步IO操作,如网络请求、文件读写等。通过协程,可以在进行IO时切换到其他任务,从而提高程序的并发性和响应性能。
  2. 并发任务处理:协程可以同时执行多个任务,从而实现并发处理。这对于处理大量的计算密集型任务或者分布式任务非常有用。协程可以在一个线程内切换执行,减少了线程切换的开销,提高了程序的性能。
  3. 复杂控制流程:协程可以简化复杂的控制流程的实现。通过使用协程,可以将复杂的任务拆分为多个子任务,每个子任务以协程的方式执行,并且可以方便地进行任务之间的通信与同步。这样可以使代码结构更加清晰、易于理解和维护。
  4. 事件驱动编程:协程可以用于实现事件驱动的编程模型,通过监听各种事件并响应相应的回调函数,从而实现非阻塞的事件处理。

一、 协程

协程不是计算机提供的,程序员人为创造的(被称为微线程)

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术,简而言之,其实就是通过一个线程实现代码块相互切换执行。

实现协程有这几种方法:

  • greenlet(早期第三方模块)
  • 通过yield 关键字
  • 再python 3.4引入了asyncio的标准库模块通过它的装饰器
  • async、await(py3.5引入)【官方推荐】
1. 通过 greenlet实现协程
from greenlet import greenlet(pip3 install greenlet)

def func1():
    print(1)       # 第一步:输出 1
    gr2.switch()   # 第三步:切换到func2函数
    print(2)       # 第6步:输出2
    gr2.switch()   # 第七步:切换到func2函数,从上一次执行的位置继续向后执行
def func2():
    print(3)       # 第四步:输出3
    gr1.switch()   # 第五步:切换到func1函数,从上一次的执行的位置继续向后执行
    print(4)       # 第八步:输出4
gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch()    # 第一步:去执行 func1函数
2. 通过yield(作为了解即可)
def func1()
    yield 1
    yield from func2
    yield 2
def func2()    
    yield 3
    yield 4
    
f1 = func1()
for item in f1:
    print(item)
3. asyncio模块实现协程

python3.4以后的版本可用 遇到IO阻塞自动切换

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)   # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)

@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)

tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
4. async & await 关键字 3.5版本之后
import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)   # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)

async def func2():
    print(3)
    await asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)

tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
5. 协程的意义

在一个线程中如果遇到IO等待的时间,线程不会傻傻的等待,而是利用空闲的时候再去干点其他的任务。

6. 并发、并行、同步、异步、阻塞、非阻塞
  • 并发、并行

    • 并发是指一段时间内,有几个程序在同一个CPU上运行,但是任意时刻只有一个程序在CPU上运行
    • 并行是指任意时刻点上,有多个程序同时运行在多个cpu上, 比如 :四个程序就分别运行在四个cpu,程序数量和cpu数量一致。
  • 同步、异步

    • 同步是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
    • 异步是指代码调用IO操作时,不必等IO操作完成就返回的调用方式(多线程、线程池)
  • 阻塞、非阻塞

    • 阻塞是指调用函数时候当前线程被挂起

    • 非阻塞是指调用函数时候当前线程不会被挂起,而是立即返回

      同步和异步
      同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。换句话说就是调用者主动接收这个结果。
      
      异步,就是调用者发出一个调用后,不用等结果,直接可以进行下一步。也就是说这个调用没有返回结果,是被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。
      
      通俗点说:
      同步就像是正在苦苦追求一个女生的男生,这天他向这个女生表白,女生要给他一个是否同意交往的回答,女生没有回答之前他会一直等这个结果。
      异步就像是个海王,广撒网,精准捕捞。群发表白消息,不管第一个女生是否给了回答,反正就是给列表里所有的女生都发了表白信息。等有女生同意和他交往,他就收到了想要的答案(返回值)。
      
      阻塞非阻塞
      阻塞和非阻塞关注得是程序在等待调用结果(消息,返回值)时的状态
      
      阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
      非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
      还是上面那个例子,你向一个女生表白,然后她如果一直吊着你不给你答复(绿茶),你为了她茶饭不思,这就形成了阻塞。
      如果你是海王,这个女生没有给你答复,你可以转过头去看看其他女生是否给你反馈,只要时不时回来看看这个女生有没有返回什么信息即可,这就是非阻塞。
      
      PS:这部分内容学起来还真的是挺绕的...
      
再简单点理解就是:

1.同步,就是我调用一个功能,该功能没有结束前,我死等结果

2.异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)

3.阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回

4.非阻塞,就是调用我(函数),我(函数)立即返回,通过select调用通知者

同步IO和异步IO的区别在于:数据拷贝时进程是否阻塞

阻塞IO和非阻塞IO的区别在于:应用程序的调用结果是否立即返回


7. 五种I/O模型
7.1 阻塞式I/O
7.2 非阻塞式I/O
7.3 I/O复用
7.4 信号驱动式I/O
7.5 异步I/O(POSIX的aio_系列函数)
7.6 总结

IO分两阶段:

1.数据准备阶段
2.内核空间复制回用户进程缓冲区阶段

阻塞IO:给女神发一条短信, 说我来找你了, 然后就默默的一直等着女神下楼, 这个期间除了等待你不会做其他事情。
非阻塞IO:给女神发短信, 如果不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会做其他事情。
IO多路复用:是找一个宿管大妈来帮你监视下楼的女生, 这个期间你可以些其他的事情. 例如可以顺便看看其他妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们的区别是什么?

1) select大妈 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子。

2) poll大妈不限制盯着女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神。

3) epoll大妈不限制盯着女生的数量, 并且也不需要一个一个去问. 那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大妈再通知你。

        上面这四种情况都是同步IO,它们有一个共同点就是, 当女神走出宿舍门口的时候, 你已经站在宿舍门口等着女神了。

异步IO的情况:
        你告诉女神我来了, 然后你就去打游戏了, 一直到女神下楼了, 发现找不见你了, 女神再给你打电话通知你, 说我下楼了, 你在哪呢? 这时候你才来到宿舍门口。
8. 生成器
  • next()

  • send()

  • throw()

  • close()

8.1 yield from
#####  yield 和 yield from的区别

def g1(iterable):
    yield iterable
def g2(iterable):
    yield from iterable

for value1 in g1(range(10)):
    print(value1)
for value2 in g2(range(10)):
    print(value2)
# python3.3中 新加了 yield from语法
from itertools import chain

my_list = [1, 2, 3]
my_dict = {
    "bobby1": "http://projectsedu.com",
    "bobby2": "http://www.imoc.com"
}

# 使用yield
def my_chain(*args, **kwargs):
    for my_iterable in args:
        for value in my_iterable:
            yield value
# 使用 yield from
def my_chain(*args, **kwargs):
    for my_iterable in args:
         yield from my_iterable

for value in my_chain(my_list, my_dict, range(5)):
    print(value)

文章来源:https://blog.csdn.net/qq_42746084/article/details/135179956
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。