30. 协程

1.协程的概念

1.1 定义

进程是操作系统内部运行的程序

线程是进程内部运行的程序

协程是线程内部运行的程序

协程是单线程下的并发,又成微线程,英文名coroutine

1.2 协程的优点

 协程切换的开销更小

GIL锁导致同一时刻只能运行一个线程,一个线程内不会限制协程数,单线程就可以实现并发的效果,最大程度利用CPU

1.3 协程的缺点

协程的本质是单线程下的多任务处理,无法利用多核优势

如果协程发生阻塞,在没有使用异步I/O的情况下,那么整个线程将阻塞,所在线程的其它协程任务都不能运行

2. 协程的操作

2.1 协程函数

加上async关键字的函数就是协程函数

async def f1():
    ...

2.2 协程对象

协程函数调用后的返回值就是协程对象

async def f1():
    print(111)


res = f1()
print(res)

 按一般函数的调用方法调用协程函数不会报错,但是会发出警告

 按一般函数的调用方法调用协程函数,函数内部的代码不会执行,只是会返回一个协程对象。

2.3 asyncio模块

Python 3.4:asyncio 被正式引入标准库,是一个实现异步编程的模块。
Python 3.5:引入了 async 和 await 关键字,这使得协程的定义和使用更加直观和简洁。这些关键字取代了早期版本中的 @asyncio.coroutine 装饰器和 yield from 语法。

2.4 事件循环的概念

事件循环可以类比while循环来理解,在循环周期内运行一些任务,特定的条件下结束循环。

import asyncio

loop = asyncio.get_event_loop()

2.5 协程函数的两种调用方法,以解释器3.10版本为例

由协程对象的定义可知,按函数名( )无法调用协程函数,需要协程对象和事件循环配合才能实现

[1]方法一:

import asyncio

async def work():
    print(666)
    return 'work函数的返回值'

def create_coroutine():
    obj = work()  # 1.调用协程函数,生成协程对象
    circle = asyncio.get_event_loop()  # 2.建立一个事件循环
    circle.run_until_complete(obj)  # 3.将协程对象当作任务提交到事件循环的任务列表中,协程运行完成事件循环停止

if __name__ == '__main__':
    create_coroutine()

在 Python 3.10 中,这个警告是因为在没有活动事件循环的主线程中调用 asyncio.get_event_loop() 时,get_event_loop() 会自动创建一个新的事件循环,这种方式被认为是不推荐的。
为了避免这个警告,可以使用 asyncio.run() 来运行协程,这是从 Python 3.7 开始推荐的做法。asyncio.run() 会自动创建和关闭事件循环,简化了异步代码的编写。

 [2]方法二:

import asyncio

async def work():
    print(666)
    return 'work函数的返回值'

def create_coroutine():
    obj = work()  # 1.调用协程函数,生成协程对象
    asyncio.run(obj)  # 2.调用run函数启动协程对象

if __name__ == '__main__':
    create_coroutine()

方式二的本质和方式一是一样的,内部先建立事件循环,然后运行run_until_complete

需要注意的是,run函数在解释器3.7才加入

2.6 await关键字

await关键字用于等待一个async(异步)函数的结果,使用await可以让程序在等待某个操作完成的同时不阻塞整个事件循环,从而允许其它任务运行,对于I/O密集型的应用特别有用。

await关键字解决了一个协程阻塞而导致整个线程阻塞的问题。

注意事项:

1.使用await必须在async(异步)函数中,不能在非异步函数中使用。

2.await只能用于异步函数返回的协程对象。

3.在异步函数内部,await可以用来等待另一个异步函数的结果,这会暂停当前协程的运行,直到等待的协程完成,然后继续运行。

代码示例

import asyncio

async def f1():
    print('异步函数运行开始')
    await asyncio.sleep(1)  # 模拟异步操作,比如网络请求
    print('异步函数运行结束')
    return 666

async def f2():
    res = await f1()
    print(res)

asyncio.run(f2())  # 获取事件循环并运行f2()协程

分析:

f1是一个异步函数,使用了await来等待asyncio.sleep(1)。这实际上并不会让程序等待1秒,而是允许事件循环在这1秒内执行其它任务。

f2也是一个异步函数,等待f1的运行结果。

ascyncio.run(f2())启动事件循环并运行f2协程。

 2.7 Task对象

[1]概念

前面的协程代码都只创建了一个任务,即事件循环的列表中只有一个任务对象;如需在程序中创建多个任务对象,需要使用Task。

Task用于并发调度协程,通过asyncio.create(协程对象)的方式创建Task对象,可以让协程加入事件循环中等待被调度执行。

注意事项:

asyncio.create_task( )函数在python3.7中被加入,在之前的版本可以改用底层级的asyncio.ensure_future( )函数。

[2]创建多任务方式一

逐个创建任务

import asyncio

# 定义协程功能函数
async def work():
    print('协程运行开始')
    await asyncio.sleep(1)  # 模拟异步操作,比如网络请求
    print('协程运行结束')
    return 666

# 定义产生协程函数
async def create_coroutine():
    print('产生协程的函数运行开始')
    task1 = asyncio.create_task(work())  # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
    task2 = asyncio.create_task(work())  # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环

    response1 = await task1  # 引用了await之后,task1遇到sleep不会阻塞整个线程
    response2 = await task2

    print(response1, response2)
    print('产生协程的函数运行结束')

if __name__ == '__main__':
    asyncio.run(create_coroutine())

 [3]创建多任务方式二

使用列表生成式生成任务

import asyncio

# 定义协程功能函数
async def work(num):
    print('协程运行开始')
    await asyncio.sleep(1)  # 模拟异步操作,比如网络请求
    print('协程运行结束')
    return num * num

# 定义产生协程函数
async def create_coroutine():
    print('产生协程的函数运行开始')
    # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
    task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]

    # 引用了await之后,task1遇到sleep不会阻塞整个线程
    # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中
    # wait里面要放可迭代对象
    done, pending = await asyncio.wait(task_list, timeout=None)
    print(done)
    print(pending)
    print('产生协程的函数运行结束')

if __name__ == '__main__':
    asyncio.run(create_coroutine())

 [3]获取协程返回值

在步骤[2]的基础上使用async.gather()获取返回值

import asyncio

# 定义协程功能函数
async def work(num):
    print('协程运行开始')
    await asyncio.sleep(1)  # 模拟异步操作,比如网络请求
    print('协程运行结束')
    return num * 10

# 定义产生协程函数
async def create_coroutine():
    print('产生协程的函数运行开始')
    # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
    task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]

    # 引用了await之后,task1遇到sleep不会阻塞整个线程
    # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中
    # wait里面要放可迭代对象
    done, pending = await asyncio.wait(task_list, timeout=None)
    response = await asyncio.gather(*task_list)
    print(response)
    print('产生协程的函数运行结束')

if __name__ == '__main__':
    asyncio.run(create_coroutine())

 需要注意的是,以上代码即使不运行 done, pending = await asyncio.wait(task_list, timeout=None),也能获取到返回值。

 

posted @ 2024-10-04 22:20  hbutmeng  阅读(7)  评论(0编辑  收藏  举报