展开

文章目录

修改历史

历史修改版本

  1. 2021-02-06 14:51:00
  2. 2021-02-02 15:28:18

Python中的asyncio简单介绍

2021-02-02 15:27:58 Python 238

简介

近些年来,我们听得最多的词语就是高并发,然而规划30000r/s上线100r/d,但是不管怎么样在这一行还是得更新自己的知识储备,不然总有一天会被淘汰der。

1.协程

我们经常听到过进程、线程可能很少有听到协程这个概念,但是相信大家肯定都听说过携程,但此协程非彼携程,下图可以比较好说明进程、线程、协程三者之间的关系。

协程

那么协程相对于线程有哪些优势呢?

  1. 我们都知道不管线程还是进程之间的切换调度都需要使用系统的资源,而协程的切换不是属于多线程或者多进程,而是在单个线程中进行切换的,相当于是子程序的切换,因此相对多线程数量越多的应用,协程的优势就更加明显了;

  2. 在多线程中如果我们需要在不同的线程中控制同一个变量,我们就需要用到锁的机制,但是协程是在同一个线程中执行的,因此不会出现同时写变量的冲突了,不需要对共享资源加锁,只需要判断执行状态,效率肯定是优于多线程的执行方式的;

 举一个简单的消费者生产者的例子,传统的方式是两个线程一个线程写消息一个线程读取消息,通过锁机制控制队列和等待,但是如果程序猿的水平不过关很容易出现死锁的情况。如果使用协程的方式呢?Python中有一个yield的关键字(这个关键字Lua语言中也有),通过该关键字可以简单的模拟一下协程,下面的程序"抄袭"的廖雪峰网站的示例

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming {}'.format(n))
        r = '200 OK'


def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing {}...'.format(n))
        r = c.send(n)
        print('[PRODUCER] Consumer return:{}'.format(r))
    c.close()


con = consumer()
produce(con)

程序的执行结果如下

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1
[PRODUCER] Consumer return:200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2
[PRODUCER] Consumer return:200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3
[PRODUCER] Consumer return:200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4
[PRODUCER] Consumer return:200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5
[PRODUCER] Consumer return:200 OK

在函数consumer中使用了yield关键字,所以该函数不是一个函数了而是一个生成器,然后将其传入到produce函数中

  1. 首先调用send()函数启动生成器;

  2. 如果produce中生产了东西,则通过c.send(n)切换到生成器consumer中执行;

  3. consumer通过yield拿到消息,又通过yield返回结果;

  4. produce拿到consumer处理的结果,继续生产下一条消息;

  5. produce决定不生产了,通过c.close()关闭consumer,整个生产消费过程

2.asyncio

asyncio是Python中用来编写并发代码的库。使用 async/await 语法。asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。asyncio 往往是构建 IO 密集型和高层级 结构化 网络代码的最佳选择。

3.简单的例子

要真正运行一个协程,asyncio 提供了三种主要机制:

  • asyncio.run() 函数用来运行最高层级的入口点 "main()" 函数 (参见上面的示例。)

  • 等待一个协程。以下代码段会在等待 1 秒后打印 "hello",然后 再次 等待 2 秒后打印 "world":

a.运行一个协程

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(f"started at {time.strftime('%X')}")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())
started at 13:48:14
hello world
finished at 13:48:17

b.并发执行

在上面的代码中我们并没有看到两个函数调用并发执行的效果,要是实现并发执行的效果我们需要用到asyncio.create_task()函数,修改上面的代码

import asyncio
import time

async def say_after(delay, what):
        await asyncio.sleep(delay)
        print(what)


async def main():
        t1 = asyncio.create_task(say_after(1, 'hello'))
        t2 = asyncio.create_task(say_after(2, 'world'))
        print(f"started at {time.strftime('%X')}")
        await t1
        await t2
        print(f"finished at {time.strftime('%X')}")

asyncio.run(main())
started at 13:57:33
hello world
finished at 13:57:35

可以看到运行两个协程只花费了2s的时间,完成了协程并发执行的功能。

c.可等待对象

在Python中如果一个对象可以在await语句中使用,那么这个对象就是可等待对象,可等待对象主要有三种类型:协程任务Future

  • 协程

    1. 协程函数:通过async def形式定义的函数;

    2. 协程对象:调用协程函数所返回的对象;

  • task

    通过task我们可以并发执行协程,当一个协程通过asyncio.create_task()函数打包成一个任务,该协程将会自动进入schedule中准备立即运行

d.休眠

通过asyncio.sleep函数可以阻塞指定的秒数,下面的程序每隔一秒输出当前时间

import asyncio
import datetime

async def display_date():
    loop = asyncio.get_running_loop()
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)

asyncio.run(display_date())
2021-02-02 14:20:52.537776
2021-02-02 14:20:53.539077
2021-02-02 14:20:54.540297
2021-02-02 14:20:55.541478
2021-02-02 14:20:56.542681

e.并发运行任务2

通过asyncio.gather(*aws, loop=None, return_exceptions=False)函数可以并发执行asw序列中的可等待对象。

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    # Schedule three calls *concurrently*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())
Task A is runnning at 14:59:22
Task B is runnning at 14:59:22
Task C is runnning at 14:59:22
Task A is finished at 14:59:23
Task C is finished at 14:59:23

将函数中的关键字参数return_exceptions设置为False(默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。

import asyncio
import time 

async def divid(name, number):
	print(f"Task {name} is runnning at {time.strftime('%X')}")
	ret = 12 / number
	await asyncio.sleep(1)
	print(f"Task {name} is finished at {time.strftime('%X')}")


async def main():
	await asyncio.gather(
		divid('A', 3),
		divid('B', 0),
		divid('C', 3)
	)

asyncio.run(main())
Task A is runnning at 15:11:55
Task B is runnning at 15:11:55
Task C is runnning at 15:11:55
Traceback (most recent call last):
  File "test7.py", line 18, in <module>
    asyncio.run(main())
  File "/usr/local/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
    return future.result()
  File "test7.py", line 15, in main
    divid('C', 3),
  File "test7.py", line 6, in divid
    ret = 12 / number
ZeroDivisionError: division by zero

关于asyncio暂时介绍这些东西,因为我也是在摸索阶段,如有不正确的地方欢迎指正。 

当前共有0条评论