Python 请教 Python 的 coroutine 的 task 的同步语义

femto · 2023年08月05日 · 最后由 ironboxer 回复于 2023年08月07日 · 302 次阅读
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()) 

output:

started at 17:13:52
hello
world

finished at 17:13:55

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

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

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

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

Note that expected output now shows that the snippet runs 1 second faster than before: output:

started at 17:14:32
hello
world
finished at 17:14:34

可以看出 task 比 coroutine 快了一秒,也就是 python 是同步执行多个 task 的, 我知道 coroutine 只有一个操作系统线程,yield 来 yield 去,那么 asyncio 是怎么实现 task 的, 有同步问题么?(即一个 task 访问修改一个数据结构,另一个 task 同时访问修改该数据结构, 会 corrupt 该数据结构么?)

import asyncio
import time

async def say_after(delay, what):
    print(f'[%s]: starting %s' % (time.time(), what))
    await asyncio.sleep(delay)
    print(f'[%s]: ending %s' % (time.time(), what))


async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    task3 = asyncio.create_task(
        say_after(0.5, 'Hai')
    )

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

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    # await task2

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


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

started at 11:49:41
[1691380181.884058]: starting hello
[1691380181.884095]: starting world
[1691380181.884105]: starting Hai
[1691380182.385623]: ending Hai
[1691380182.885086]: ending hello
finished at 11:49:42

create_task 会顺序把三个任务放入队列。 await 相当于一把发令枪,trigger 一下,三个任务顺序开始执行。

如果此时,在 await task1 之后,再 sleep 一下,就能看到执行时间最长的那个任务也结束了。

import asyncio
import time

async def say_after(delay, what):
    print(f'[%s]: starting %s' % (time.time(), what))
    await asyncio.sleep(delay)
    print(f'[%s]: ending %s' % (time.time(), what))


async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    task3 = asyncio.create_task(
        say_after(0.5, 'Hai')
    )

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

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    # await task2

    await asyncio.sleep(2)

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


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

started at 11:56:07
[1691380567.491296]: starting hello
[1691380567.491312]: starting world
[1691380567.491318]: starting Hai
[1691380567.992693]: ending Hai
[1691380568.4926028]: ending hello
[1691380569.492554]: ending world
finished at 11:56:10

应该把这里的 create_task, await 比作 socket 中的 writeflush 操作来理解,或者写日志中的 writeflush 操作。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号