python异步IO和协程

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

本文对python协程做一些总结,来自平时的学习和一些博客资料


1. 异步IO

关于异步IO的讲解,网上资料太多了,我以前的文章也提到过,此处只做一些简单介绍:

  • 异步IO采用消息循环的模式,重复“读取消息—处理消息”的过程
  • 消息模型解决等待IO操作的问题:
    • 程序发出IO请求,直接结束本轮消息处理,进入下一轮消息的处理
    • 当IO操作完成守,将收到一条IO完成的消息,处理该消息时获取IO操作的结果
    • 在IO操作的这段时间里,异步模型可以循环处理其他操作,而且没有线程切换的消耗,同时处理多个IO请求,适用于大多数IO密集型的应用程序。

python 的异步协程 (async coroutine) ,在提交 HTTP 请求后,就没必要等待请求完成再进一步操作,而是可以一边等着请求完成,一边做着其他工作。这可能在逻辑上需要多些思考来保证程序正确运行,但是好处是可以利用更少的资源做更多的事。

2. 协程概念

协程是python异步IO中常用的一个概率,英文为coroutine。协程不同于进程和线程,在某种程度上,它的概念更接近于函数调用的概念。

2.1 函数调用:

我们知道,程序执行中,函数调用是通过函数栈实现,因为栈FILO的特点,通常函数调用过程都是:A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。子程序(函数)调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

2.1 协程调用:

协程和函数最相似的地方就是协程本身的定义就更函数很像,而其调用也很相像。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程针对的是一个线程中的函数调用之间,所以没有线程切换,是在一个线程中轮流执行和终端多个函数而已,所以效率较高,而且不需要锁机制(只有一个线程执行)

需要注意的是,子程序内部中断的不是函数调用,而是被调用函数中断(一般来说可能是一条执行命令需要很长时间等待结果返回,比如常见的IO操作),转而去执行另一个函数(不是调用另一个函数),类似两个函数轮流执行,没有发生函数调用。

上述的执行流程看上去很像多线程有木有!!!实际上,一些协程的执行结果也很像多线程的执行结果,因为本质就是不同代码段轮流执行,进而提高效率。协程得到运用主要在于其效率更高:

协程的执行效率较高,因为不是线程切换,而是程序控制,没有开销。因为是一个线程中有多个协程,所以不需要锁机制。

此外,多线程编程中锁机制的开销在协程中也不存在,因为协程不需要锁机制:

因为协程执行是在一个线程中,不存在同时写变量冲突,所以共享资源不需要加锁,只需要判断状态就好,进一步提升效率。

3. python中的协程操作

python中协程的支持是通过生成器(generator)实现的。

熟悉generator的知道:函数定义时,将函数的结果通过yield返回,则定义了一个生成器,而在生成器中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

而对于协程来说,yield不但可以返回一个值,它还能接收函数调用者发出的参数。(这句话很重要)

下面运用网上教程讲解的一个例子(我觉的很清晰)来说明协程操作:

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

代码如下:

def consumer():
    r = ''
    while True:
        n = yield r # 返回
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n) # 调用
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

要看懂上述代码,最重要的就是两个关键字的执行:send 和yield

  • send:

熟悉python生成器的知道,调用生成器的next(),将运行到yield位置,此时暂停执行环境,并返回这条语句yield关键词后面跟随的值。 这是next()的使用方法。

而send本质和next相似,都是执行generator直到yield,不同的是send可以发送数据给yield表达式。

更重要的是,send发送的参数成为yield表达式的值!(通过这种方式赋值)

  • yield:

普通函数return隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方。

而”yield”的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。

在python中,拥有这种能力的“函数”被称为生成器,只要只要函数中包含yield关键字,该函数调用就是生成器对象。生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字而不是return。一个def主体中包含yield,则这个def主体自动变为generator

函数中每次使用yield产生一个值,函数就返回该值,然后停止执行,等待被激活,被激活后继续在原来的位置执行。

3.1 代码执行分析

熟悉了这两个关键字,来分析上述代码:

两个函数的定义不多说,生成一个consumer函数对象c,然后从produce(c)开始,注意第一条语句:

c.send(None)

这是启动一个生成器的操作,再次强调,send和next操作都是调用生成器,而第一调用生成器就是启动生成器,启动生成器必须使用next()语句或是send(None)启动生成器,不能使用send发送一个非None的值

此处需要强调:

这个操作其实已经执行了produce和consumer中的一部分,具体如下:

  • send类似next,调用了consumer,然后一直执行到yield语句,因为send调用发送的函数参数是None,None作为n = yield r 中yield表达式的结果,将None赋值给了n,因为n为None,所以if成立,return返回;

    • 需要注意的是,此时consumer是return的,相当于本次生成器调用结束,而不是yield。
  • c.send(None) 表达式的结果为n = yield r中yield后面的r,r初始值为'',所以c.send(None) 结果为空,但是这个结果未赋值给任何变量

程序继续执行到:

n = n + 1
print()
r = c.send(n)

此时n = 1, 然后打印一行信息(生产了1),关键就是send语句。这条语句执行会有以下结果:

  • c.send调用了生成器consumer的对象c,得到的是c中yield后面的值(yield返回结果)
  • send发送了一个值n(=1),作为调用生成器的参数,并调用执行consumer();
  • consumer生成器的对象c 获得这个调用参数,执行consumer(),一直到yield语句;
  • consumer中yield表达式的结果就是send语句发送的参数n,然后将这个结果赋值给consumer中的n;
  • 然后print打印Consuming信息,r赋值为‘200 0K’,循环到yield停止,返回r,这个返回值作为produce中c.send(n)调用结果,赋值给produce中的r,然后打印。

如此循环5次,直到c.close()关闭生成器consumer,结束。整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

Donald Knuth的一句话总结协程的特点:“子程序就是协程的一种特例。”

3.2 python3.5中的async和await

上面只是协程的简单操作,python中对协程的编程模型引入了一些标准库。

在 python3.5 中,创建一个协程仅仅只需使用 async 关键字,而python3.4使用 @asyncio.coroutine 装饰器。下面的任一代码,都可以作为协程工作,形式上也是等同的:

import asyncio

async def ping_server(ip):  # 3.5
    pass

@asyncio.coroutine
def load_file(path):   # 3.4
    pass

这俩特殊的函数,在调用时会返回协程对象。

3.1 中协程操作只是简单的生成器调用,常见的我们还需要在生成器或者说协程之间相互调用,用到yield from。yield from 用于一个generator调用另一个generator,主要是为了generator之间的调用。

yield from 表达式的使用方式如下:

import asyncio

@asyncio.coroutine
def get_jason(client, url):
       file_content = yield from load_file('/Usrs/scott/data.txt')

正如所看到的, yield from 被使用在用 @asyncio.coroutine 装饰的函数内(将这个函数装饰成一个生成器),如果想把 yield from 在这个函数外使用,将会抛出如下语法错误:

File "main.py", line 1
    file_content = yield from load_file('/Users/scott/data.txt')
                          ^
SyntaxError: 'yield' outside function

较新的语法是使用 async/await 关键字。 async 从 Python3.5 开始被引进,跟 @asyncio.coroutine 装饰器一样,用来声明一个函数是一个协程。只要把它放在函数定义之前,就可以应用到函数上,使用方式如下:

async def ping_server(ip):
        # ping code here...

实际调用这个函数时,使用 await 而不用 yield from,当然,使用方式依然差不多:

async def ping_local(ip):
         return await ping_server('192.168.1.1')

再强调一遍,跟 yield from 一样,不能在函数外部使用 await,否则会抛出语法错误。 (async 用来声明一个函数是协程,然后使用 await调用这个协程, await 必须在函数内部,这个函数通常也被声明为另一个协程)

Python3.5 对这两种调用协程的方法都提供了支持,但是推荐 async/await 作为首选。

4. Event Loop

asyncio是python异步IO的标准支持,其编程模型就是一个消息循环,其中事件循环模块就是核心。

如果不知道如何开始和操作一个 Eventloop,那么上诉有关协程所说的都起不了多大作用。Eventloop 在执行异步函数时非常重要,重要到只要执行协程,基本上就得利用 Eventloop 。

Eventloop 提供了相当多的功能:

  • 注册,执行和取消延迟调用(异步函数)
  • 创建客户端与服务端传输用于通信
  • 创建子程序和通道跟其他的程序进行通信
  • 指定函数的调用到线程池

Eventloop 有相当多的配置和类型可供使用,但大部分程序只需要如下方式预定函数即可:

import asyncio

async def hello():
        print("Hello World")
        r = await asyncio.sleep(1)
        print("Again")

loop = asyncio.get_event_loop()
loop.run_until_complete(hello())
loop.close()

分析上述代码:async把一个函数标记为coroutine类型,这个函数调用会返回协程对象。然后,我们就把这个coroutine扔到EventLoop中执行。

hello()会首先打印出Hello world,然后,await语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

加粗部分不好理解,如果我们往EventLoop中添加多个任务,就能看出效果。

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

结果:

Hello world! (<_MainThread(MainThread, started 140735195337472)>)
Hello world! (<_MainThread(MainThread, started 140735195337472)>)
(暂停约1秒)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)

由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。

(上述用例引自:摸我

需要注意,loop.run_until_complete() 函数实际上是阻塞性的,也就是在所有异步方法完成之前,它是不会返回的。

本文其他参考资料:
http://mp.weixin.qq.com/s?__biz=MjM5NzU0MzU0Nw==&mid=402216131&idx=2&sn=3017018d74d71a398e3a7aeb56ca5a7c&scene=19#wechat_redirect

Python异步IO协程 参考自仓库协程(coroutine)在多任务协作中体现的效率又极为的突出。Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中均存在一些缺点,因此引出了协程。在计算机中CPU的运算速率要远远大于IO速率,而当CPU运算完毕后,如果再要闲置很长时间去等待IO任务完成才能进行下一个任务的计算,这样的任务执行效率很低。所以需要有一种异步的方式来处理类似任务。 阅读详情

相关推荐

python协程异步并发,同步与阻塞,异步与非阻塞,Python异步IO协程与同步原语介绍,协程的优势劣势

python多线程:协程异步并发,同步与阻塞,异步与非阻塞,Python异步IO协程与同步原语介绍,协程的优势劣势

m0_55284524的博客 1838

Python进阶】协程异步 IO

协程异步 IO1. 协程的概念2. yield 的使用3. send 发送数据4. 异步 IOasyncio协程5. asyncio6. 定义一个协程7. 创建一个 task8. 绑定回调9. future 与 result10. 阻塞 await11. 并发并行12. 协程嵌套13. 协程停止 1. 协程的概念 协程,又称微线程,纤程。英文名 Coroutine,是一种用户态的轻量级线程。 子程序,或者称为函数,在所有语言中都是层级调用,比如 A 调用 B,B 在执行过程 中又调用了 C,C

全网粉丝30W+,CSDN、稀土掘金人工智能领域签约作者,人工智能、大模型、Python 爬虫,商务合作,请私信沟通。 1061

Python并发编程:异步IO协程

异步IO协程Python并发编程中的重要技术,它们能够显著提高IO密集型任务的性能。通过合理使用asyncio协程语法,可以编写出高效、响应迅速的并发程序。本文提供的代码示例表格分析展示了异步IO协程在实际应用中的使用技巧优势,希望对相关开发研究有所帮助。

shejizuopin的博客 811

Python异步IO协程(一):从yield from到async的使用

引言:协程(coroutine)是Python中一直较为难理解的知识,但其在多任务协作中体现的效率又极为的突出。众所周知,Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中均存在一些缺点。因此,我们引出了协程。 为什么需要协程? 首先,我们需要知道同步异步是什么东东,不知道的看详解。 简单来说: 【同步】:就是发出一个“调用”时,在没有得到结果之前,该“调用”就不...

SL_World的博客 2万+

pyinstaller报错SyntaxError: 'yield' inside async function

参考地址https://github.com/pyinstaller/pyinstaller/issues/2434 解决办法 安装最新的版本 pip install git+https://github.com/pyinstaller/pyinstaller.git

雜貨鋪 6886

yield from语句

result = yield from B() # B是一个coroutine 当正在被主线程执行的的coroutine遇到 yield from语句时,当前coroutine会被挂起,yield from 后面的coroutine 会被主线程执行。 例子import asyncio @asyncio.coroutine def print_sum(x, y): result = yie

zhouxinkai311的专栏 1069

yield from

#coding:utf-8def g1(x): yield range(x) def g2(x): yield from range(x) it1 = g1(5) it2 = g2(5)print([x for x in it1]) print([x for x in it2])E:\Anaconda3\python.exe F:/py_project/test_del.py [ra

YouAreLion的博客 268

what yield does in python?

http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python yield->generator(生成器)->iterable(迭代器) Everything you can use "for... in..." on is an iterable: lists, strings,

haipengdai的博客 6193

python 协程异步IO

又称微线程,是一种用户态的轻量级线程。与操作系统层面的线程不同,协程的调度完全由用户控制,无需操作系统介入。这使得协程的切换开销远低于线程,能够实现更高的并发性能。Python中的协程主要依赖asyncio库实现。asyncioPython 3.4版本引入的标准库,用于编写单线程并发代码。它使用事件循环(event loop)来调度执行协程异步IO是一种非阻塞的IO操作模式。在异步IO中,当应用程序发起IO请求时,它不会等待IO操作完成,而是立即返回并继续执行其他任务。

Chujun123528的博客 1686

Python 异步IO-协程

在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。

分享编程知识与技巧 致力于技术交流与学习 找我合作私聊 有偿解决计算机问题 1198

Python异步IO协程简介

1、协程(coroutine) 协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般灵活,但在实践中使用没有子例程那样广泛。协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文

GVFDBDF的专栏 3412

Python学习:异步IO:协程asyncio

所谓协程就是在一个线程中切换子进程,相比多线程有如下好处:最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,多线程比,线程数量越多,协程的性能优势就越明显。第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。python协程通过gene

pyb的博客 1万+

Python学习篇29-协程异步IO

协程异步IO(协程概念(yield的使用、send发送数据))、异步IO协程asyncio、定义一个协程、创建一个task、绑定回调、future与result、阻塞await、并发并行、协程嵌套、协程停止)

Txtechcom的博客 365

Python 异步IO--协程

异步IO:例如爬虫中,访问一个链接后,会有需要等待服务器响应的时间,在等待过程中,异步编程可以进行其他操作,如请求其他连接,等到服务器返回响应时,得到通知,再进行处理。 协程是在单线程中运行的,所以不是并行运行的。一个协程可以放弃执行,把机会让给其它协程Python3.4中,协程都是通过使用yield fromasyncio模块中的@asyncio.coroutine来实现的。async...

铁马冰河入梦来 900

Python 异步 IO协程asyncioasync/await、aiohttp

From :廖雪峰异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Await入门指南 :https://zhuanlan.zhihu.com/p/27258289 Python 生成器 yield 关键字:https:/...

墨鱼菜鸡 1690

python - 协程异步IO(asyncio)

什么是协程,为什么要使用协程? 由于GIL的存在,导致Python多线程性能甚至比单线程更糟。 GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程。 于是出现了协程(Coroutine)这么个东西。 协程: 协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执

Rnan_prince的博客 1288

python中的异步 IO 协程

在传统的同步IO中,当一个IO操作发生时,程序会阻塞在那里,直到这个IO操作完成。而在异步IO中,当一个IO操作发生时,程序会继续执行其他任务,而不需要等待该IO操作完成。异步IO协程在处理高并发IO密集型任务时非常有用,可以提高程序的性能响应性。但是需要注意,使用异步IO协程需要谨慎处理共享资源并发控制,以避免出现竞态条件线程安全问题。在Python中,异步IOAsynchronous IO协程(Coroutines)是处理并发异步编程的两种重要技术。,它模拟了一个IO操作。

qq_52695936的博客 303

python异步io协程处理文件_异步IO协程 (asyncio)

同步与异步同步:先执行第一个事务,如果遇到阻塞(time.sleep()),会一直等待,直到第一个事务执行完毕,才会执行第二个事务异步:与同步是相对的,指执行第一个事务的时候,如果遇到阻塞,会直接执行第二个事务,不会等待。通过状态、通知、回调来调用处理结果异步进程、线程都可以实现异步,用协程也可以实现异步使用异步 IO,无非是提高我们写的软件系统的并发。 这个软件系统, 可以是网络爬虫,也可以是 ...

weixin_39765209的博客 325
上一篇: C++之STL查找
下一篇: git子模块更新的两个错误解决
Parle
博客等级 码龄14年 36粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值