导航:首页 > 编程语言 > python协程例子

python协程例子

发布时间:2022-09-11 19:11:00

‘壹’ python里怎么实现多个协程一起执行,只要完

需要使用新的函数as_completed()来实现,可以把多个并发的协程一起给它,但它把返回的结果变成一个生成器,每次返回一个协程的结果,与函数wait()一样,执行协程是乱序的,不会等所有协程执行完成才返回。例子:

importasyncio


asyncdefphase(i):
print('inphase{}'.format(i))
awaitasyncio.sleep(0.5-(0.1*i))
print('donewithphase{}'.format(i))
return'phase{}result'.format(i)


asyncdefmain(num_phases):
print('startingmain')
phases=[
phase(i)
foriinrange(num_phases)
]
print('waitingforphasestocomplete')
results=[]
fornext_to_completeinasyncio.as_completed(phases):
answer=awaitnext_to_complete
print('receivedanswer{!r}'.format(answer))
results.append(answer)
print('results:{!r}'.format(results))
returnresults


event_loop=asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()

结果输出如下:starting main
waiting for phases to complete
in phase 2
in phase 1
in phase 0
done with phase 2
received answer 'phase 2 result'
done with phase 1
received answer 'phase 1 result'
done with phase 0
received answer 'phase 0 result'
results: ['phase 2 result', 'phase 1 result', 'phase 0 result']

‘贰’ Python协程之asyncio

asyncio 是 Python 中的异步IO库,用来编写并发协程,适用于IO阻塞且需要大量并发的场景,例如爬虫、文件读写。

asyncio 在 Python3.4 被引入,经过几个版本的迭代,特性、语法糖均有了不同程度的改进,这也使得不同版本的 Python 在 asyncio 的用法上各不相同,显得有些杂乱,以前使用的时候也是本着能用就行的原则,在写法上走了一些弯路,现在对 Python3.7+ 和 Python3.6 中 asyncio 的用法做一个梳理,以便以后能更好的使用。

协程,又称微线程,它不被操作系统内核所管理,而完全是由程序控制,协程切换花销小,因而有更高的性能。

协程可以比作子程序,不同的是,执行过程中协程可以挂起当前状态,转而执行其他协程,在适当的时候返回来接着执行,协程间的切换不需要涉及任何系统调用或任何阻塞调用,完全由协程调度器进行调度。

Python 中以 asyncio 为依赖,使用 async/await 语法进行协程的创建和使用,如下 async 语法创建一个协程函数:

在协程中除了普通函数的功能外最主要的作用就是:使用 await 语法等待另一个协程结束,这将挂起当前协程,直到另一个协程产生结果再继续执行:

asyncio.sleep() 是 asyncio 包内置的协程函数,这里模拟耗时的IO操作,上面这个协程执行到这一句会挂起当前协程而去执行其他协程,直到sleep结束,当有多个协程任务时,这种切换会让它们的IO操作并行处理。

注意,执行一个协程函数并不会真正的运行它,而是会返回一个协程对象,要使协程真正的运行,需要将它们加入到事件循环中运行,官方建议 asyncio 程序应当有一个主入口协程,用来管理所有其他的协程任务:

在 Python3.7+ 中,运行这个 asyncio 程序只需要一句: asyncio.run(main()) ,而在 Python3.6 中,需要手动获取事件循环并加入协程任务:

事件循环就是一个循环队列,对其中的协程进行调度执行,当把一个协程加入循环,这个协程创建的其他协程都会自动加入到当前事件循环中。

其实协程对象也不是直接运行,而是被封装成一个个待执行的 Task ,大多数情况下 asyncio 会帮我们进行封装,我们也可以提前自行封装 Task 来获得对协程更多的控制权,注意,封装 Task 需要 当前线程有正在运行的事件循环 ,否则将引 RuntimeError,这也就是官方建议使用主入口协程的原因,如果在主入口协程之外创建任务就需要先手动获取事件循环然后使用底层方法 loop.create_task() ,而在主入口协程之内是一定有正在运行的循环的。任务创建后便有了状态,可以查看运行情况,查看结果,取消任务等:

asyncio.create_task() 是 Python3.7 加入的高层级API,在 Python3.6,需要使用低层级API asyncio.ensure_future() 来创建 Future,Future 也是一个管理协程运行状态的对象,与 Task 没有本质上的区别。

通常,一个含有一系列并发协程的程序写法如下(Python3.7+):

并发运行多个协程任务的关键就是 asyncio.gather(*tasks) ,它接受多个协程任务并将它们加入到事件循环,所有任务都运行完成后会返回结果列表,这里我们也没有手动封装 Task,因为 gather 函数会自动封装。

并发运行还有另一个方法 asyncio.wait(tasks) ,它们的区别是:

‘叁’ 详解Python中的协程,为什么说它的底层是生成器

协程又称为是微线程,英文名是Coroutine。它和线程一样可以调度,但是不同的是线程的启动和调度需要通过操作系统来处理。并且线程的启动和销毁需要涉及一些操作系统的变量申请和销毁处理,需要的时间比较长。而协程呢,它的调度和销毁都是程序自己来控制的,因此它更加轻量级也更加灵活。

协程有这么多优点,自然也会有一些缺点,其中最大的缺点就是需要编程语言自己支持,否则的话需要开发者自己通过一些方法来实现协程。对于大部分语言来说,都不支持这一机制。go语言由于天然支持协程,并且支持得非常好,使得它广受好评,短短几年时间就迅速流行起来。

对于Python来说,本身就有着一个GIL这个巨大的先天问题。GIL是Python的全局锁,在它的限制下一个Python进程同一时间只能同时执行一个线程,即使是在多核心的机器当中。这就大大影响了Python的性能,尤其是在CPU密集型的工作上。所以为了提升Python的性能,很多开发者想出了使用多进程+协程的方式。一开始是开发者自行实现的,后来在Python3.4的版本当中,官方也收入了这个功能,因此目前可以光明正大地说,Python是支持协程的语言了。

生成器(generator)

生成器我们也在之前的文章当中介绍过,为什么我们介绍协程需要用到生成器呢,是因为Python的协程底层就是通过生成器来实现的。

通过生成器来实现协程的原因也很简单,我们都知道协程需要切换挂起,而生成器当中有一个yield关键字,刚好可以实现这个功能。所以当初那些自己在Python当中开发协程功能的程序员都是通过生成器来实现的,我们想要理解Python当中协程的运用,就必须从最原始的生成器开始。

生成器我们很熟悉了,本质上就是带有yield这个关键词的函数。

async,await和future

从Python3.5版本开始,引入了async,await和future。我们来简单说说它们各自的用途,其中async其实就是@asyncio.coroutine,用途是完全一样的。同样await代替的是yield from,意为等待另外一个协程结束。

我们用这两个一改,上面的代码就成了:

async def test(k):

n = 0

while n < k:

await asyncio.sleep(0.5)

print('n = {}'.format(n))

n += 1

由于我们加上了await,所以每次在打印之前都会等待0.5秒。我们把await换成yield from也是一样的,只不过用await更加直观也更加贴合协程的含义。

Future其实可以看成是一个信号量,我们创建一个全局的future,当一个协程执行完成之后,将结果存入这个future当中。其他的协程可以await future来实现阻塞。我们来看一个例子就明白了:

future = asyncio.Future()

async def test(k):

n = 0

while n < k:

await asyncio.sleep(0.5)

print('n = {}'.format(n))

n += 1

future.set_result('success')

async def log():

result = await future

print(result)

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait([

log(),

test(5)

]))

loop.close()

在这个例子当中我们创建了两个协程,第一个协程是每隔0.5秒print一个数字,在print完成之后把success写入到future当中。第二个协程就是等待future当中的数据,之后print出来。

在loop当中我们要调度执行的不再是一个协程对象了而是两个,所以我们用asyncio当中的wait将这两个对象包起来。只有当wait当中的两个对象执行结束,wait才会结束。loop等待的是wait的结束,而wait等待的是传入其中的协程的结束,这就形成了一个依赖循环,等价于这两个协程对象结束,loop才会结束。

总结

async并不只是可以用在函数上,事实上还有很多其他的用法,比如用在with语句上,用在for循环上等等。这些用法比较小众,细节也很多,就不一一展开了,大家感兴趣的可以自行去了解一下。

不知道大家在读这篇文章的过程当中有没有觉得有些费劲,如果有的话,其实是很正常的。原因也很简单,因为Python原生是不支持协程这个概念的,所以在一开始设计的时候也没有做这方面的准备,是后来觉得有必要才加入的。那么作为后面加入的内容,必然会对原先的很多内容产生影响,尤其是协程借助了之前生成器的概念来实现的,那么必然会有很多耦合不清楚的情况。这也是这一块的语法很乱,对初学者不友好的原因。

‘肆’ 如何用python写一个协程

我学习了asyncio的协程,现在在我的印象中一个协程有两个要素:
* 用`asyncio.coroutine`装饰
* 用`yield from`调用其他协程
我想要了解协程是什么,所以做了以下尝试。
我经过尝试,发现运行构造出来的协程得到的是一个`generator`(迭代器)。
而最常规的迭代器生成使用的是`yield`。
所以同样是生成迭代器,那协程是否可以用`yield`而不是`yield from`。
我经过尝试,发现协程的调用有特殊的方式。
而最常规的迭代器都是直接调用就可以的。
所以,同样是函数,那协程是否可以脱离`event_loop`(消息循环)调用。
我还尝试过通过`yield`构造一个协程。
没有报错也运行成功了,所以应该没有问题。

‘伍’ python中的协程是怎么实现多任务的

协程也称为微线程,是在一个线程中,通过不断的切换任务函数实现了多任务的效果。
协程在python实现的原理主要是通过yield这个关键字实现
但是真正在开发时,可以不需要自己实现,可以通过很多成熟的第三方模块来实现协程,比如greenlet,gevent等模块。黑马程序员可学习Python哦,有免费的学习视频,学习路线图,学习工具!

‘陆’ 如何用python写一个协程

是个很长的问题,我提供一下我理解的题主你的问题:
我学习了asyncio的协程,现在在我的印象中一个协程有两个要素:
* 用`asyncio.coroutine`装饰
* 用`yield from`调用其他协程
我想要了解协程是什么,所以做了以下尝试。
我经过尝试,发现运行构造出来的协程得到的是一个`generator`(迭代器)。
而最常规的迭代器生成使用的是`yield`。
所以同样是生成迭代器,那协程是否可以用`yield`而不是`yield from`。
我经过尝试,发现协程的调用有特殊的方式。
而最常规的迭代器都是直接调用就可以的。
所以,同样是函数,那协程是否可以脱离`event_loop`(消息循环)调用。
我还尝试过通过`yield`构造一个协程。
没有报错也运行成功了,所以应该没有问题。

‘柒’ 如何用python写一个协程

作者:LittleCoder
链接:https://www.hu.com/question/54483694/answer/139785021
来源:知乎
着作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

yield`和`yield from`的区别

`yield`题主肯定不陌生,而`yield from`是PEP 380中新增的一个特性。
PEP 380的名字是嵌套子迭代器的语法糖(我喜欢这么翻译,原文是:Syntax for Delegating to a Subgenerator)。
既然是语法糖,那么肯定本来是有别的写法的,这里给出本来的写法:
def subgen():
for i in range(3):
yield 'subgen: %s' % i
return 'subgen returned'def gen():
r = yield from subgen()
print('r = "%s"' % r)
yield rdef gen_without_yield_from():
sg = subgen()
try:
while 1:
yield sg.send(None)
except StopIteration as e:
yield e.valueprint('* [gen] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)print('* [gen_without_yield_from] get all values')for v in gen_without_yield_from():
print('get value: %s' % v)

不难看出,`yield`子迭代器是把子迭代器直接传递出去,`yield from`子迭代器是把子迭代器的值一个一个传出去。
虽然实际把子迭代器当做一个对象直接传递出去也没有问题,也有使用场景(生成迭代器的迭代器)。
但在协程中相较于这个令人愉快的语法糖而言,直接传递就显得没有必要且碍事了。
毕竟我希望使用一个子迭代器是把子迭代器中的代码都运行一遍而不是直接就把这个子迭代器传出来让我自己操作。
所以如果你把子迭代器直接传了出去,asyncio就判断你在做一件奇怪的事情并报了错。
那么,回到问题,给出的程序要怎么通过`yield`调用呢?
# 源程序@asyncio.coroutinedef hello():
print("Hello world!")
yield from asyncio.sleep(1)
print("Hello again!")# 使用[email protected] hello():
print("Hello world!")
for v in asyncio.sleep(1):
yield v
print("Hello again!")

协程和迭代器的区别

举个比喻,迭代器和协程就像火药和枪械,利用火药的特性辅助各种其他东西才造出了枪械。
迭代器就最简单的本质而言就是一个可以暂停的程序。
那么就有这样一个合理的联想,我是不是可以节省下所有不必要的例如等待网站响应的等待时间。
就是我把我的请求发过去以后就把这个程序暂停下来,开启别的程序,等到响应来了再叫我回到这个程序。
那么等待网站响应的时间也就完全没有浪费了,比原来傻傻的等着网站响应真是优秀了许多。
这就是协程。
所以,为什么看上去都是`generator`,迭代器不会天生成为协程呢?
因为没有一个知道什么时候应该叫你回到这个程序的人。
这个人就是`event_loop`(消息循环)。
回到问题,协程是否可以脱离`event_loop`(消息循环)调用。
讲道理是不可以的,但合理联想一下是不是一直不停的告诉程序又到你了就行了。
像这样:
@asyncio.coroutinedef gen():
for i in range(3):
yield ifor i in gen():
print(i)print('end')

的确有些协程这样是可以运行的(这些协程为什么要写成协程?)。
但终究你是在不应该告诉程序到你的时候告诉了他这件事情。
所以显然获取数据的话当时数据根本没有传到,`sleep`的话就根本没有了`sleep`的效果。
只是看上去能够运行,实际完全没有用。
asyncio还为此特地加了一个断言,如果你这样调用`asyncio.sleep`,asyncio会发现你在伪装消息循环骗他。
协程的原理

这是另一个看上去能够运行,实际上完全没有用的事情。
这虽然不是你想问的问题,但你已经碰到了也迟早会意识到,所以一并讲了。
这个问题应该是这样的:为什么我写出来的协程完全没有协程的效果?
import time, [email protected] sleep(symbol, i):
time.sleep(i)
print('[%s] finished')loop = asyncio.get_event_loop()tasks = [sleep('A', 2), sleep('B', 2)]loop.run_until_complete(asyncio.wait(tasks))loop.close()

看到这里你起码可以简单的讲出来,因为显然我们在傻傻的等。
我们没有在开始等待的时候把程序暂停下来,然后在等待结束后继续运行程序,我们一心一意的在等。
我们真的`time.sleep`了两秒,而不是去做了两秒其他的事情。
你有各种选择,可以花式等待。我这里给你两个最基本的例子:
* get请求
* 同步变为协程(线程池)
get请求
为了让你更好的了解asyncio,我从最底层的socket开始写一个协程的get请求给你。
为了模拟延时很大的网站,我在本地开了一个延时服务器,这是服务器程序。
import tornado.ioloopimport tornado.webfrom tornado.gen import coroutine, sleepclass MainHandler(tornado.web.RequestHandler):
@coroutine
def get(self, waitTime=3):
yield sleep(int(waitTime))
self.write('you have waited for %ss' % waitTime)if __name__ == "__main__":
application = tornado.web.Application([
('/([0-9])', MainHandler),
], debug=True)
application.listen(5000)
try:
tornado.ioloop.IOLoop.current().start()
except:
tornado.ioloop.IOLoop.current().stop()

记得打开了这个服务器再运行下面的程序。
import socket, asyncio, timedata = 'GET /%s HTTP/1.1\r\n\r\n'loop = asyncio.get_event_loop()@asyncio.coroutinedef get(i):
future = asyncio.futures.Future(loop=loop)
s = socket.socket()
s.connect(('127.0.0.1', 5000))
s.sendall((data % i).encode('utf8'))
s.setblocking(False)
def callback(future):
future.set_result(s.recv(999).split(b'\r\n\r\n')[-1])
loop.add_reader(s.fileno(), callback, future)
r = yield from future
print('Return value: %s' % r)tasks = [get(3), get(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()

同步变为协程(线程池)
这里拿sleep模拟耗时的程序,原理就是开了5个新的线程处理耗时程序。
当然实际的`asyncio.sleep`只需要告诉消息循环一定时间后叫醒我就好了。
import asyncio, sleep, [email protected] sleep(i):
executor = concurrent.futures.ThreadPoolExecutor(5)
future = asyncio.futures.wrap_future(executor.submit(time.sleep, i), loop=loop)
yield from future
print('Slept for %s seconds' % i)tasks = [sleep(3), sleep(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()

‘捌’ python中多进程+协程的使用以及为什么要用它

python里推荐用多进程而不是多线程,但是多进程也有其自己的限制:相比线程更加笨重、切换耗时更长,并且在python的多进程下,进程数量不推荐超过CPU核心数(一个进程只有一个GIL,所以一个进程只能跑满一个CPU),因为一个进程占用一个CPU时能充分利用机器的性能,但是进程多了就会出现频繁的进程切换,反而得不偿失。
不过特殊情况(特指IO密集型任务)下,多线程是比多进程好用的。
举个例子:给你200W条url,需要你把每个url对应的页面抓取保存起来,这种时候,单单使用多进程,效果肯定是很差的。为什么呢?
例如每次请求的等待时间是2秒,那么如下(忽略cpu计算时间):
1、单进程+单线程:需要2秒*200W=400W秒==1111.11个小时==46.3天,这个速度明显是不能接受的
2、单进程+多线程:例如我们在这个进程中开了10个多线程,比1中能够提升10倍速度,也就是大约4.63天能够完成200W条抓取,请注意,这里的实际执行是:线程1遇见了阻塞,CPU切换到线程2去执行,遇见阻塞又切换到线程3等等,10个线程都阻塞后,这个进程就阻塞了,而直到某个线程阻塞完成后,这个进程才能继续执行,所以速度上提升大约能到10倍(这里忽略了线程切换带来的开销,实际上的提升应该是不能达到10倍的),但是需要考虑的是线程的切换也是有开销的,所以不能无限的启动多线程(开200W个线程肯定是不靠谱的)
3、多进程+多线程:这里就厉害了,一般来说也有很多人用这个方法,多进程下,每个进程都能占一个cpu,而多线程从一定程度上绕过了阻塞的等待,所以比单进程下的多线程又更好使了,例如我们开10个进程,每个进程里开20W个线程,执行的速度理论上是比单进程开200W个线程快10倍以上的(为什么是10倍以上而不是10倍,主要是cpu切换200W个线程的消耗肯定比切换20W个进程大得多,考虑到这部分开销,所以是10倍以上)。
还有更好的方法吗?答案是肯定的,它就是:
4、协程,使用它之前我们先讲讲what/why/how(它是什么/为什么用它/怎么使用它)
what:
协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。
why:
目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
因为协程是用户自己来编写调度逻辑的,对CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。
how:
python里面怎么使用协程?答案是使用gevent,使用方法:看这里
使用协程,可以不受线程开销的限制,我尝试过一次把20W条url放在单进程的协程里执行,完全没问题。
所以最推荐的方法,是多进程+协程(可以看作是每个进程里都是单线程,而这个单线程是协程化的)
多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来,这种方式对于数据量较大的爬虫还有文件读写之类的效率提升是巨大的。

‘玖’ python里协程事件循环里怎么样调用非协程函数

为了管理协程和I/O的回调函数,asyncio库的事件循环也能基于定时的方式调用普通的函数,使用call_soon()函数,例子如下:

importasyncio
importfunctools
defcallback(arg,*,kwarg='default'):
print('callbackinvokedwith{}and{}'.format(arg,kwarg))
asyncdefmain(loop):
print('registeringcallbacks')
loop.call_soon(callback,1)
wrapped=functools.partial(callback,kwarg='notdefault')
loop.call_soon(wrapped,2)
awaitasyncio.sleep(0.1)
event_loop=asyncio.get_event_loop()
try:
print('enteringeventloop')
event_loop.run_until_complete(main(event_loop))
finally:
print('closingeventloop')
event_loop.close()


结果输出如下:
entering event loop
registering callbacks
callback invoked with 1 and default
callback invoked with 2 and not default
closing event loop

‘拾’ python里并发执行协程时部分阻塞超时怎么办

碰到这种需求时不要惊慌,可以使用wait()里的timeout参数来设置等待时间,也就是从这个函数开始运行算起,如果时间到达协程没有执行完成,就可以不再等它们了,直接从wait()函数里返回,返回之后就可以判断那些没有执行成功的,可以把这些协程取消掉。例子如下

importasyncio


asyncdefphase(i):
print('inphase{}'.format(i))
try:
awaitasyncio.sleep(0.1*i)
exceptasyncio.CancelledError:
print('phase{}canceled'.format(i))
raise
else:
print('donewithphase{}'.format(i))
return'phase{}result'.format(i)


asyncdefmain(num_phases):
print('startingmain')
phases=[
phase(i)
foriinrange(num_phases)
]
print('waiting0.1forphasestocomplete')
completed,pending=awaitasyncio.wait(phases,timeout=0.1)
print('{}completedand{}pending'.format(
len(completed),len(pending),
))
#
#asweexitwithoutfinishingthem.
ifpending:
print('cancelingtasks')
fortinpending:
t.cancel()
print('exitingmain')


event_loop=asyncio.get_event_loop()
try:
event_loop.run_until_complete(main(3))
finally:
event_loop.close()

结果输出如下:

starting main
waiting 0.1 for phases to complete
in phase 0
in phase 2
in phase 1
done with phase 0
1 completed and 2 pending
canceling tasks
exiting main
phase 1 canceled
phase 2 canceled

阅读全文

与python协程例子相关的资料

热点内容
代码编译运行用什么软件 浏览:997
动态库在程序编译时会被连接到 浏览:759
python超简单编程 浏览:258
获取命令方 浏览:976
怎样制作文件夹和图片 浏览:58
调研编译写信息 浏览:861
python冯诺依曼 浏览:418
同时安装多个app有什么影响 浏览:254
奥术杀戮命令宏 浏览:184
用sdes加密明文字母e 浏览:361
单片机原理及应用试题 浏览:425
易语言开启指定文件夹 浏览:40
马思纯参加密室大逃脱 浏览:322
文件夹冬季浇筑温度 浏览:712
京东有返点的aPp叫什么 浏览:603
如何查看u点家庭服务器是几兆 浏览:262
python应用接口怎么接 浏览:67
腐蚀怎么进不去服务器啊 浏览:359
linuxcpiogz 浏览:631
安卓中的布局是什么文件 浏览:397