鱼C论坛

 找回密码
 立即注册
查看: 2375|回复: 16

协程封装问题 这个差异疯了, 跪求大佬解答

[复制链接]
发表于 2021-10-31 10:19:24 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
下面是2个不同的封装方式, 但是线程的使用却有明显的不同,一个是可以乱序的,一个是按顺序执行的,求大神解答。 不知道原因。
import asyncio
import time
async def ceshi01(x,y):
    await asyncio.sleep(y)
    print(f"我是{x},我运行了{y}分钟")

async def yunxing():
    group_a = asyncio.gather(ceshi01('A',5),ceshi01('a',3))  #线程a
    group_b = asyncio.gather(ceshi01('b',4),ceshi01('B',2))  #线程b
    await group_a
    await group_b
print(asyncio.run(yunxing()))

运行结果是:          结果是按时间排序的, 效率最高。
我是B,我运行了2分钟
我是a,我运行了3分钟
我是b,我运行了4分钟
我是A,我运行了5分钟

另外一个封装方式
import asyncio
import time
async def ceshi01(x,y):
    await asyncio.sleep(y)
    print(f"我是{x},我运行了{y}分钟")

async def yunxing():
    await  asyncio.gather(ceshi01('A',5),ceshi01('a',3))   
    await  asyncio.gather(ceshi01('b',4),ceshi01('B',2))
    await  asyncio.gather(ceshi01('c',1),ceshi01('c',1))

print(asyncio.run(yunxing()))
运行结果是:                 这里的结果是按顺序执行的        从a到c   
我是a,我运行了3分钟
我是A,我运行了5分钟
我是B,我运行了2分钟
我是b,我运行了4分钟
我是c,我运行了1分钟
我是c,我运行了1分钟
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-10-31 10:44:39 | 显示全部楼层
基本上大部分程序语言都是单一线程的,意思就是先做完一个事情,然后才做另外一个事情,这是肯定的。
Python 附有 asyncio 模组里的 async、await 用于解决同步和异步问题,简单的说就是当你执行一个函数时,系统不会等待返回结果,直接运行下一个函数(看似双线程,其实是单线程)
用容易理解方式解说:async 就好象告诉系统,我要同时执行多个函数,不用等到结束函数时才执行另外一个。而 await asyncio.sleep() 如同 time.sleep(),唯一差别就是 await 用于提醒系统,还有多久记得回来看这个函数,不然函数不会结束(RuntimeWarning: coroutine 'xxx' was never awaited)
我的解说肯定没有网路上的好,你也可以自行网路查找解说,解说百百种。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-31 10:56:07 | 显示全部楼层
傻眼貓咪 发表于 2021-10-31 10:44
基本上大部分程序语言都是单一线程的,意思就是先做完一个事情,然后才做另外一个事情,这是肯定的。
Pyth ...

额  还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 11:15:12 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 10:56
额  还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。

这是优先执行问题

第一个封装会先检测外面的 await (外面两个 await 都有联系)然后再检测 ceshi01() 函数里的 await,所以 group_a 和 group_b 是同时检测的。

第二个封装外面的三个 await 并没有联系,所以真正是一个个执行,由上往下

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 11:17:40 | 显示全部楼层
本帖最后由 傻眼貓咪 于 2021-10-31 11:23 编辑
从入门到富豪 发表于 2021-10-31 10:56
额  还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。


为了避免这种问题,最好创建事件循环 asyncio.get_event_loop() 和 待处理事件列表,想执行时,则用 run_until_complete() 执行便可。如:
  1. import asyncio
  2. import time

  3. async def A():
  4.     await asyncio.sleep(3)
  5.     print("我是洗衣机 A")

  6. async def B():
  7.     await asyncio.sleep(1)
  8.     print("我是洗衣机 B")

  9. async def C():
  10.     await asyncio.sleep(2)
  11.     print("我是洗衣机 C")

  12. loop = asyncio.get_event_loop() # 事件循环
  13. task = [A(), B(), C()] # 待处理事件

  14. start = time.time()
  15. loop.run_until_complete(asyncio.wait(task)) # 执行所有事件
  16. end = time.time()
  17. loop.close()
  18. print(f"一共耗时 {int(end - start)} 秒")
复制代码
  1. 我是洗衣机 B
  2. 我是洗衣机 C
  3. 我是洗衣机 A
  4. 一共耗时 3 秒
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 12:17:28 | 显示全部楼层
无论是create_task还是asyncio.gather都会把协程对象及其各种状态打包注册为 Task 对象,
你可以把它的作用当成是放入任务队列中,并返回

    task1 = asyncio.create_task(coro1)
    task2 = asyncio.create_task(coro1)
    await task1
    await task2

无疑是往队列塞进去2个 再清空队列

    await  asyncio.create_task(coro1)
    await  asyncio.create_task(coro1)

则是 塞进去了就清空队列

asyncio.gather的返回值是保住了参数顺序的  无关乎执行顺序
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 12:24:13 | 显示全部楼层
建议去学一下JavaScript的asyncfunction和Promise对象 你这个自然就通了   它和py的Future对象差不多

我都没怎么看python的asyncio都会了  然后去学个fastapi岂不美哉

js只有asyncfunction和其返回的Promise对象两层 毕竟事件循环是自带的  缺点是没有sleep函数能卡住进程

而py有asyncfunction 返回coro 然后coro打包成task task是Future的子类 3层结构   事件循环还得手动开启
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 12:26:19 | 显示全部楼层
最重要的思想其实就一个   生产者消费者模型 而已
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-31 13:15:09 | 显示全部楼层
kogawananari 发表于 2021-10-31 12:17
无论是create_task还是asyncio.gather都会把协程对象及其各种状态打包注册为 Task 对象,
你可以把它的作 ...

清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情

我专门搞个栗子,gather的列表不是按顺序执行的,  假设是按顺序执行的, 结果的返回也不是按顺序返回的, 我这个是因为有个迭代器,才保证取值顺序。
mylist = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
class AA():
    def __init__(self):
        self.g = self.getone() #生成了迭代器,放在init里面
    def getone(self):
        for i in mylist:
            yield i  #返回子集列表,返回的顺序是a—z

    async def xieru(self,id,delay):

            while True:
                await asyncio.sleep(delay)
                # time.sleep(delay)
                print(id,next(self.g))

    async def main(self):
        task1 =asyncio.create_task(self.xieru("AA",4)) #线程AA 执行的内部顺序abc,def,等是固定的
        task2 =asyncio.create_task(self.xieru("BB",2))  #线程BB
        task3 = asyncio.create_task(self.xieru("CC",3))  #线程CC
        tasks = [task1,task2,task3]
        await asyncio.gather(*tasks)  #gather返回的列表,并不能按带入的顺序排列,而是按实际执行完成的顺序排列。

if __name__ =='__main__':
    aa = AA()
    asyncio.run(aa.main()) #运行的程序要放在最外面
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-31 13:17:54 | 显示全部楼层
傻眼貓咪 发表于 2021-10-31 11:17
为了避免这种问题,最好创建事件循环 asyncio.get_event_loop() 和 待处理事件列表,想执行时,则用 ru ...

目前python采用了高级的 gather来设置,不用手动用loop了,我感觉还是对await的 对象的 理解不够深刻。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 13:20:09 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 13:15
清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情

import asyncio

async def count(m):
    await asyncio.sleep(3)
    print(m)
    return m
async def main():
    aws = [count(i) for i in range(10)]
    print(await asyncio.gather(*aws))
asyncio.run(main())

按顺序返回
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 13:21:25 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 13:17
目前python采用了高级的 gather来设置,不用手动用loop了,我感觉还是对await的 对象的 理解不够深刻。

抱歉,帮不到你了,兄弟
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 13:34:13 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 13:15
清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情

import asyncio

async def count1():
    await asyncio.sleep(1)
    print("\n\n----------任务队列里目前有----------")
    for task in asyncio.all_tasks():
        print(task.get_coro())
    print('----------count1任务运行完毕----------\n\n')
async def count2():
    await asyncio.sleep(2)
    print("\n\n----------任务队列里目前有----------")
    for task in asyncio.all_tasks():
        print(task.get_coro())
    print('----------count2任务运行完毕----------\n\n')
async def main():
    '''await asyncio.create_task(count1())
    await asyncio.create_task(count2())'''
    task1 = asyncio.create_task(count1())
    task2 = asyncio.create_task(count2())
    await task1
    await task2
    print('\n----------main任务运行完毕----------\n')
asyncio.run(main())

我帮你把任务改成了输出当前所有任务   你再把注释放出来换成直接await 对比一下
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-31 22:59:54 | 显示全部楼层
kogawananari 发表于 2021-10-31 13:34
import asyncio

async def count1():

亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 23:24:50 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 22:59
亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。

一个是继发执行 一个是并发执行

输出的任务队列一看就知道区别了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-31 23:32:14 | 显示全部楼层
从入门到富豪 发表于 2021-10-31 22:59
亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。

继发运行的时候 队列塞进去一个任务马上就把那个任务做了 明显每次只打印了自己和main

并发运行的时候 队列塞进去两个一起执行的  执行的时候打印出了自己和main 之间还有其他任务
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-11-1 10:45:05 | 显示全部楼层
kogawananari 发表于 2021-10-31 23:32
继发运行的时候 队列塞进去一个任务马上就把那个任务做了 明显每次只打印了自己和main

并发运行的时候 ...

这个结果我知道呀, 问题是为啥会这样呢, 这个写法执行逻辑或者顺序是如何操作的呢, 毕竟从写法来讲, 2个写法几乎是对等的呀。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-5-12 06:49

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表