Python异步和线程不是并行工作的



下面的代码是为了测试目的而编写的。

它只是一个调用";5000x阶乘(900(";并打印输出

不管我使用线程还是异步,它们总是一个接一个地运行一个函数,从不并行。

第一个异步:

import asyncio
async def factorial(name, number):
def fatorial(n):
if n == 1:
return 1
else:
return n * fatorial(n - 1)
print(f"START: Task {name}: factorial({number})")
for i in range(5000):
var = fatorial(number)
print(f"FIM: Task {name}: factorial({number})")
return var
async def main():
task1 = asyncio.ensure_future(factorial("A", 900))
task2 = asyncio.ensure_future(factorial("B", 900))
task3 = asyncio.ensure_future(factorial("C", 900))
task4 = asyncio.ensure_future(factorial("D", 900))
await asyncio.gather(task1, task2, task3, task4)

asyncio.run(main())

也尝试过:

async def main():
# Schedule three calls *concurrently*:
task1 = asyncio.create_task(factorial("A", 900))
task2 = asyncio.create_task(factorial("B", 900))
task3 = asyncio.create_task(factorial("C", 900))
task4 = asyncio.create_task(factorial("D", 900))
await task4

asyncio.run(main())

还尝试了线程:

import threading
def factorial(name, number):
def fatorial(n):
if n == 1:
return 1
else:
return n * fatorial(n - 1)
print(f"START: Task {name}: factorial({number})")
for i in range(5000):
var = fatorial(number)
print(f"FIM: Task {name}: factorial({number})")

threading.Thread(target=factorial("A", 900), daemon=True).start()
threading.Thread(target=factorial("B", 900), daemon=True).start()
threading.Thread(target=factorial("C", 900), daemon=True).start()
threading.Thread(target=factorial("D", 900), daemon=True).start()

并且输出总是相同的:

START: Task A: factorial(900)
FIM: Task A: factorial(900)
START: Task B: factorial(900)
FIM: Task B: factorial(900)
START: Task C: factorial(900)
FIM: Task C: factorial(900)
START: Task D: factorial(900)
FIM: Task D: factorial(900)

正如评论中所说,这些都不适合CPU绑定的工作-但是的,它们可以在一定程度上并行运行-这只需要比按顺序运行花费一些时间,或者多一点额外的时间。你的代码只是在两种情况下都不正确

对于asyncio位:asyncio只需在遇到await指令或等效指令时停止一个任务以运行另一个任务。没有";等待";您的代码一直运行到完成,没有切换任务的机会。

await自然出现在最适合异步运行的代码中,因为它们被放置在对I/O的调用之前,这应该需要一段时间(例如,您等待对数据库服务器的查询或http请求(。在这样一个CPU绑定的循环中,没有什么可等待的,所以,如果你想表现得很好,并让其他代码并行运行,你必须引入那些";孔";。一种方法是等待对asyncio.sleep的呼叫。如果你把一个这样的调用放在内部阶乘的末尾,你应该看到它并行运行:

import asyncio
async def factorial(name, number):
async def fatorial(n):
await asyncio.sleep(0)
if n == 1:
return 1
else:
return n * fatorial(n - 1)
print(f"START: Task {name}: factorial({number})")
for i in range(5000):
var = await fatorial(number)
print(f"FIM: Task {name}: factorial({number})")
return var

在线程化的情况下,有一个不同的错误。与async def声明的函数不同,当创建以函数为目标的线程时,不能调用函数。您只需将函数作为对象传递给线程构造函数,并将参数分别传递给每个构造函数。实际的函数调用将在线程内部进行。按照您的方法,您只是在创建每个线程的调用发生之前急切地调用了所有函数(对threading.Thread(...)的调用中括号内的所有内容都必须在调用发生之前执行。

";async def函数";(正确的名称是"协同程序函数"(是不同的:调用语法(例如factorial()(解析了,但这不会运行任何代码——相反,对协同程序函数的调用会产生一个协同程序对象——然后可以等待或封装在任务或将来:只有它们才能真正执行函数体中的代码。

因此,对于线程化代码,更改如下:


threading.Thread(target=factorial, args=("A", 900), daemon=True).start()
threading.Thread(target=factorial, args=("B", 900), daemon=True).start()
threading.Thread(target=factorial, args=("C", 900), daemon=True).start()
threading.Thread(target=factorial, args=("D", 900), daemon=True).start()

现在,如果您有一台多核机器,您可以将线程示例更改为多处理示例,在创建Process实例时也要同样小心,并且您应该看到您的执行时间与您拥有的物理CPU核的数量成比例下降。(最多4个,因为您只创建4个并行任务(

如果简化内部函数,可以更容易地看到发生了什么。就像这样。

import asyncio
import time
async def factorial(name, number):
print(f"START: Task {name}: factorial({number})")
if True:  # or False - change manually
await asyncio.sleep(1.0)
else:
time.sleep(1.0)
print(f"FIM: Task {name}: factorial({number})")
async def main():
task1 = asyncio.ensure_future(factorial("A", 900))
task2 = asyncio.ensure_future(factorial("B", 900))
task3 = asyncio.ensure_future(factorial("C", 900))
task4 = asyncio.ensure_future(factorial("D", 900))
await asyncio.gather(task1, task2, task3, task4)
asyncio.run(main())

True(而非CPU绑定(

START: Task A: factorial(900)
START: Task B: factorial(900)
START: Task C: factorial(900)
START: Task D: factorial(900)
FIM: Task A: factorial(900)
FIM: Task C: factorial(900)
FIM: Task B: factorial(900)
FIM: Task D: factorial(900)

False(也不受CPU限制(

START: Task A: factorial(900)
FIM: Task A: factorial(900)
START: Task B: factorial(900)
FIM: Task B: factorial(900)
START: Task C: factorial(900)
FIM: Task C: factorial(900)
START: Task D: factorial(900)
FIM: Task D: factorial(900)

不同之处在于,等待asyncio.sleep将控制权交还给事件循环。调用time.sleep没有。

最新更新