众所周知,asyncio旨在加快服务器速度,增强了它作为Web服务器承载更多请求的能力。但是,根据今天的测试,我震惊地发现,出于任务之间切换的目的,使用Thread的速度比使用协程的速度要快得多(尽管在保证线程锁定的情况下)。这是否意味着使用协程是没有意义的?
想知道为什么,有人可以帮我弄清楚吗?
这是我的测试代码:依次在两个任务中添加一个全局变量2000000次。
from threading import Thread , Lock
import time , asyncio
def thread_speed_test():
def add1():
nonlocal count
for i in range(single_test_num):
mutex.acquire()
count += 1
mutex.release()
mutex = Lock()
count = 0
thread_list = list()
for i in range(thread_num):
thread_list.append(Thread(target = add1))
st_time = time.time()
for thr in thread_list:
thr.start()
for thr in thread_list:
thr.join()
ed_time = time.time()
print("runtime" , count)
print(f'threading finished in {round(ed_time - st_time,4)}s ,speed {round(single_test_num * thread_num / (ed_time - st_time),4)}q/s' ,end='\n\n')
def asyncio_speed_test():
count = 0
@asyncio.coroutine
def switch():
yield
async def add1():
nonlocal count
for i in range(single_test_num):
count += 1
await switch()
async def main():
tasks = asyncio.gather( *(add1() for i in range(thread_num))
)
st_time = time.time()
await tasks
ed_time = time.time()
print("runtime" , count)
print(f'asyncio finished in {round(ed_time - st_time,4)}s ,speed {round(single_test_num * thread_num / (ed_time - st_time),4)}q/s')
asyncio.run(main())
if __name__ == "__main__":
single_test_num = 1000000
thread_num = 2
thread_speed_test()
asyncio_speed_test()
在我的电脑中得到以下结果:
2000000
threading finished in 0.9332s ,speed 2143159.1985q/s
2000000
asyncio finished in 16.044s ,speed 124657.3379q/s
追加:
我意识到,当线程数量增加时,线程模式变慢,而异步模式变快。 这是我的测试结果:
# asyncio #
thread_num numbers of switching in 1sec average time of a single switch(ns)
2 122296 8176
32 243502 4106
128 252571 3959
512 253258 3948
4096 239334 4178
# threading #
thread_num numbers of switching in 1sec average time of a single switch(ns)
2 2278386 438
4 737829 1350
8 393786 2539
16 367123 2720
32 369260 2708
64 381061 2624
512 381403 2622
答案 0 :(得分:0)
我不确定,您可能正在将苹果与橘子进行比较。
您基本上是在惩罚异步,这迫使它切换上下文,这需要时间,而允许线程自由运行。
asyncio被认为用于必须等待一段时间输入的任务。在您的基准测试中情况并非如此。
为了公平地比较,您应该模拟一些现实的延迟。
答案 1 :(得分:0)
为了进行更公平的比较,我对您的代码做了一些更改。
我用条件替换了您简单的锁。这使我可以在每次计数器迭代后强制进行线程切换。 Condition.wait()函数调用始终阻塞进行调用的线程;仅当另一个线程调用Condition.notify()时,该线程才继续。因此,必须进行线程切换。
测试不是这种情况。仅当线程调度程序导致任务切换时,才会发生任务切换,因为代码的逻辑永远不会导致线程阻塞。与Condition.wait()不同,Lock.release()函数不会阻止调用者。
有一个小困难:最后运行的线程在最后一次调用Condition.wait()时将永远阻塞。因此,我引入了一个简单的计数器来跟踪剩余的运行线程数。另外,当一个线程完成其循环时,它必须对Condition.notify()进行最后一次调用才能释放下一个线程。
我对异步测试所做的唯一更改是用await asyncio.sleep(0)替换了“ yield”语句。这是为了与Python 3.8兼容。我还将试用次数减少了10倍。
时间是在使用Python 3.8的相当老的Win10计算机上进行的。
如您所见,线程代码要慢很多。那就是我所期望的。拥有async / await的原因之一是因为它比线程机制轻巧。
from threading import Thread , Condition
import time , asyncio
def thread_speed_test():
def add1():
nonlocal count
nonlocal thread_count
for i in range(single_test_num):
with mutex:
mutex.notify()
count += 1
if thread_count > 1:
mutex.wait()
thread_count -= 1
with mutex:
mutex.notify()
mutex = Condition()
count = 0
thread_count = thread_num
thread_list = list()
for i in range(thread_num):
thread_list.append(Thread(target = add1))
st_time = time.time()
for thr in thread_list:
thr.start()
for thr in thread_list:
thr.join()
ed_time = time.time()
print("runtime" , count)
print(f'threading finished in {round(ed_time - st_time,4)}s ,speed {round(single_test_num * thread_num / (ed_time - st_time),4)}q/s' ,end='\n\n')
def asyncio_speed_test():
count = 0
async def switch():
await asyncio.sleep(0)
async def add1():
nonlocal count
for i in range(single_test_num):
count += 1
await switch()
async def main():
tasks = asyncio.gather(*(add1() for i in range(thread_num)) )
st_time = time.time()
await tasks
ed_time = time.time()
print("runtime" , count)
print(f'asyncio finished in {round(ed_time - st_time,4)}s ,speed {round(single_test_num * thread_num / (ed_time - st_time),4)}q/s')
asyncio.run(main())
if __name__ == "__main__":
single_test_num = 100000
thread_num = 2
thread_speed_test()
asyncio_speed_test()
runtime 200000
threading finished in 4.0335s ,speed 49584.7548q/s
runtime 200000
asyncio finished in 1.7519s ,speed 114160.9466q/s