欢迎访问yhm138的博客园博客, 你可以通过 [RSS] 的方式持续关注博客更新

MyAvatar

yhm138

HelloWorld!

Python中的协程

背景知识

以下背景知识来自文章 https://zhuanlan.zhihu.com/p/137057192

协程的实现

在Python中有多种方式可以实现协程,例如:
greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)
yield,生成器,借助生成器的特点也可以实现协程代码。
asyncio,在Python3.4中引入的模块用于编写协程代码。
async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。
说实话我在各大博客,论坛见到的各式各样,总结下来其实也就这几种

协程可以通过一个线程在多个上下文中进行来回切换执行。

生活中不也是这样的么,假设 你是一家制造汽车的老板,员工点击设备的【开始】按钮之后,在设备前需等待30分钟,然后点击【结束】按钮,此时作为老板的你一定希望这个员工在等待的那30分钟的时间去做点其他的工作。

碰到IO阻塞时间长的任务没必要一直等下去,所以涉及到异步编程。新开一个线程专门做等的任务的话,麻烦,还要考虑锁资源,消耗锁,等等。所以引入协程的概念

实战

LeetCode上有多线程的例题,这里打算尝试拿协程写一遍
!以下代码由本人编写,不保证没有隐患
有些可能只是为了出效果而出效果。。。此时建议考虑多进程,多线程
以下代码更多的是为了熟悉asyncio而不是给出问题的解决方案

lc1114. 按序打印

LeetCode上有多线程的例题,
一道easy是让你按次序打印firstsecondthird

拿Python中的协程来写,一种写法就是下面这种
TIO

import asyncio
import time

class Foo:
    def __init__(self):
        pass

    async def first(self) -> None:
        print("First",end="")
        await asyncio.sleep(2)


    async def second(self) -> None:
        print("Second",end="")
        await asyncio.sleep(2)


    async def third(self) -> None:
        print("Third",end="")
        await asyncio.sleep(2)




t_start=time.time()
loop=asyncio.get_event_loop()
a=Foo()
task1=a.first()
task2=a.second()
task3=a.third()
tasks=[task1, task2, task3]
loop.run_until_complete(asyncio.gather(*tasks))
t_end=time.time()
print("\n"+f"TOTAL_TIME:{t_end-t_start}")

运行结果如上,
解释的话,按照官方文档的说法:
协程函数: 定义形式为 async def 的函数;
协程对象: 调用 协程函数 所返回的对象。

所以3个task都是协程对象,tasks是一个迭代器,此迭代器的每个元素都是协程对象
loop=asyncio.get_event_loop()定义一个事件循环对象

这里asyncio.sleep()替代了一个高耗时的IO操作,在真实场景中可能就是一个高IO耗时的操作(比如网络IO等等)
await asyncio.sleep() 表示await后很可能是一个IO密集的操作,于是挂起,切换到下一个协程(如果有)运行

由于是异步的,总耗时大约为2s而不是约6s

lc1115. 交替打印FooBar

LeetCode上一道middle,交替打印FooBar

我写了一份协程版的代码. TIO

import asyncio
import time

class FooBar:
    def __init__(self, n):
        self.n = n

    async def foo(self) -> None:
        for i in range(self.n):
            print("Foo",end="")
            await asyncio.sleep(1)


    async def bar(self) -> None:
        for i in range(self.n):
            print("Bar",end="")
            await asyncio.sleep(1)


a=FooBar(3)

tasks = [
    asyncio.ensure_future(a.foo()),
    asyncio.ensure_future(a.bar())
]

loop = asyncio.get_event_loop()
time_start=time.time()
loop.run_until_complete(asyncio.wait(tasks))
time_end=time.time()
print("\n"+f"TOTAL_TIME:{time_end-time_start}")
FooBarFooBarFooBar
TOTAL_TIME:3.0205185413360596

进程已结束,退出代码为 0

asyncio 的同步原语

asyncio 具有下列基本同步原语:

  • Lock
  • Event
  • Condition
  • Semaphore
  • BoundedSemaphore

asyncio 原语不是线程安全的

lc1116. 打印零与奇偶数

LeetCode上一道middle,打印010203040506...这样的序列
我写了个协程版的如下,(这道题用信号量做容易想,弄3个信号量,最先开始的置1,其余置0.按照要求加减即可)

TIO

# 只是asyncio编程练手,感觉有种为了出效果而出效果的感觉
import asyncio

class ZeroEvenOdd:
    def __init__(self, n):
        self.n = n + 1
        self.Zero = asyncio.Semaphore(1)
        self.Even = asyncio.Semaphore(0)
        self.Odd = asyncio.Semaphore(0)

    async def zero(self) :
        for i in range(1, self.n):
            await self.Zero.acquire()
            try:
                print(0, end="")
            finally:
                if i % 2 == 1:
                    self.Odd.release()
                else:
                    self.Even.release()

    async def even(self) :
        for i in range(1, self.n):
            if i % 2 == 0:
                await self.Even.acquire()
                try:
                    print(i, end="")
                finally:
                    self.Zero.release()

    async def odd(self) :
        for i in range(1, self.n):
            if i % 2 == 1:
                await self.Odd.acquire()
                try:
                    print(i, end="")
                finally:
                    self.Zero.release()


a=ZeroEvenOdd(5)
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(  #由于有了同步机制,gather中协程对象的次序可以随便放
    a.odd(),
    a.zero(),
    a.even()
))

0102030405
进程已结束,退出代码为 0

参考

asyncio中文文档

posted @ 2021-06-24 19:47  yhm138  阅读(466)  评论(0编辑  收藏  举报