简体   繁体   English

如何使 Python 中的异步线程并行运行?

[英]How to make asyncio threads in Python run in parallel?

I am trying to read through the asyncio examples, but failed to find the simplest (in my viewpoint).我正在尝试通读 asyncio 示例,但找不到最简单的示例(在我看来)。 Suppose I have a "normal" function that takes 1 sec.假设我有一个“正常”的 function 需要 1 秒。 I simulate that with time.sleep(1) call.我用time.sleep(1)调用来模拟它。 How can I wrap that function in a way that three calls would run asynchronously so the total execution time would be 1 sec?如何包装 function 以使三个调用异步运行,因此总执行时间为 1 秒?

I can do it by using threads, but not asyncio.我可以通过使用线程来做到这一点,但不是 asyncio。

Here is an example:这是一个例子:

import asyncio
import time
from threading import Thread

from datetime import datetime
from math import sqrt

def heavy_cpu(n):
    print(f"{n} --> start: {datetime.now()}")
    time.sleep(1)
    # for i in range(5099999):
    #     _ = sqrt(i) * sqrt(i)
    print(f"{n} --> finish: {datetime.now()}")

async def count(n):
    await asyncio.sleep(0.0000001)
    heavy_cpu(n)

async def main_async():
    await asyncio.gather(count(1), count(2), count(3))

def test_async():
    s = time.perf_counter()
    asyncio.run(main_async())
    elapsed = time.perf_counter() - s
    print(f"asyncio executed in {elapsed:0.2f} seconds.")

# ========== asyncio vs threading =============

def main_thread():
    threads = [Thread(target=heavy_cpu, args=(n,)) for n in range(1, 4)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

def test_thread():
    s = time.perf_counter()
    main_thread()
    elapsed = time.perf_counter() - s
    print(f"thread executed in {elapsed:0.2f} seconds.")

if __name__ == "__main__":
    test_async()
    test_thread()

The output: output:

1 --> start: 2020-05-12 18:28:53.513381
1 --> finish: 2020-05-12 18:28:54.517861
2 --> start: 2020-05-12 18:28:54.518162
2 --> finish: 2020-05-12 18:28:55.521757
3 --> start: 2020-05-12 18:28:55.521930
3 --> finish: 2020-05-12 18:28:56.522813
asyncio executed in 3.01 seconds.

1 --> start: 2020-05-12 18:28:56.523789
2 --> start: 2020-05-12 18:28:56.523943
3 --> start: 2020-05-12 18:28:56.524087
1 --> finish: 2020-05-12 18:28:57.5265992 --> finish: 2020-05-12 18:28:57.526689
3 --> finish: 2020-05-12 18:28:57.526849

thread executed in 1.00 seconds.

Question: why each asyncio finish step [1,2,3] takes 1 sec each?问题:为什么每个异步finish步骤 [1,2,3] 每个需要 1 秒? How do I make it truly async?我如何使它真正异步?

Never use time.sleep in an async program;永远不要在异步程序中使用time.sleep it doesn't relinquish control to the event loop, so the event loop is blocked for the entirety of the sleep.它不会放弃对事件循环的控制,因此事件循环在整个睡眠期间被阻塞。 Replace any use of time.sleep(n) with await asyncio.sleep(n) , which puts that task to sleep and only requeues it when the sleep is up, allowing the event loop to do other work.将 time.sleep(n time.sleep(n)的任何使用替换为await asyncio.sleep(n) ,这会使该任务进入睡眠状态,并且仅在睡眠结束时重新排队,从而允许事件循环执行其他工作。

If you are in fact using time.sleep this way intentionally (you're clearly aware asyncio.sleep exists), well, that's how async works;如果您实际上是故意以这种方式使用time.sleep (您清楚地知道asyncio.sleep存在),那么这就是 async 的工作方式; any task that doesn't voluntarily give control back to the event loop (via await , directly or indirectly) will run to completion before any other task gets a chance to run.任何不主动将控制权交还给事件循环的任务(通过await直接或间接)将在任何其他任务有机会运行之前运行完成。 Asynchronous is not the same as concurrent;异步不等于并发; only async-aware activities like I/O that can be working in the background simultaneously will actually run in parallel, not normal CPU work or arbitrary blocking calls.只有像 I/O 这样可以在后台同时工作的异步感知活动才会真正并行运行,而不是正常的 CPU 工作或任意阻塞调用。

Asyncio is not magic. Asyncio 不是魔术。 If you have a function that takes 1 second to execute because it's got a lot of calculations to do, you can't make it run three times in 1 second unless you run it on three different CPU cores.如果你有一个 function 需要 1 秒执行,因为它有很多计算要做,你不能让它在 1 秒内运行 3 次,除非你在三个不同的 CPU 内核上运行它。 In other words, you have to use multiple processes.换句话说,您必须使用多个进程。 You say that you can make it run three times in 1 second with threads, but that's simply not true.你说你可以用线程让它在 1 秒内运行 3 次,但这根本不是真的。 With only one CPU core involved, it will take three seconds to run the function three times.仅涉及一个 CPU 内核,运行 function 3 次需要 3 秒。 Period.时期。 It's very simple.这很简单。 You need three seconds' worth of CPU time.您需要三秒钟的 CPU 时间。 Threads are not magic either.线程也不是魔法。

Now suppose your function takes 1 second because it's spending most of its time waiting for a resource, like a network or a peripheral.现在假设您的 function 需要 1 秒,因为它大部分时间都在等待资源,例如网络或外围设备。 Now there's a potential benefit from either threading or asyncio, depending on how the low-level functions are written.现在,线程或异步都有潜在的好处,这取决于低级函数的编写方式。 By arranging for the waits to happen in parallel you can make your function run three times in less than three seconds.通过安排等待并行发生,您可以让 function 在不到 3 秒的时间内运行 3 次。 That's the ONLY case when threading or asyncio makes your program go faster.这是线程或异步使您的程序 go 更快的唯一情况。

There may be other reasons to use threads or asyncio besides execution speed.除了执行速度之外,可能还有其他原因使用线程或异步。 In a GUI program, for example, there is typically a single thread where the GUI gets updated.例如,在 GUI 程序中,通常有一个单独的线程来更新 GUI。 If you perform a long calculation in this thread, the application will freeze until the calculation is finished.如果您在此线程中执行长时间计算,应用程序将冻结,直到计算完成。 So it's often a good idea to do the calculation in a secondary thread, or in another asyncio task if your GUI platform supports that.因此,如果您的 GUI 平台支持,在辅助线程或另一个异步任务中进行计算通常是一个好主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM