我害怕我仍然有点困惑(尽管检查其他线程)是否:
我最初的猜测是两者都没有,并且正确的异步代码应该能够在一个线程中运行 - 但是可以通过添加线程来改进它,例如:
所以我构建了这个玩具示例:
from threading import *
from queue import Queue
import time
def do_something_with_io_lag(in_work):
out = in_work
# Imagine we do some work that involves sending
# something over the internet and processing the output
# once it arrives
time.sleep(0.5) # simulate IO lag
print("Hello, bee number: ",
str(current_thread().name).replace("Thread-",""))
class WorkerBee(Thread):
def __init__(self, q):
Thread.__init__(self)
self.q = q
def run(self):
while True:
# Get some work from the queue
work_todo = self.q.get()
# This function will simiulate I/O lag
do_something_with_io_lag(work_todo)
# Remove task from the queue
self.q.task_done()
if __name__ == '__main__':
def time_me(nmbr):
number_of_worker_bees = nmbr
worktodo = ['some input for work'] * 50
# Create a queue
q = Queue()
# Fill with work
[q.put(onework) for onework in worktodo]
# Launch processes
for _ in range(number_of_worker_bees):
t = WorkerBee(q)
t.start()
# Block until queue is empty
q.join()
# Run this code in serial mode (just one worker)
%time time_me(nmbr=1)
# Wall time: 25 s
# Basically 50 requests * 0.5 seconds IO lag
# For me everything gets processed by bee number: 59
# Run this code using multi-tasking (launch 50 workers)
%time time_me(nmbr=50)
# Wall time: 507 ms
# Basically the 0.5 second IO lag + 0.07 seconds it took to launch them
# Now everything gets processed by different bees
是异步的吗?
对我来说,这段代码似乎并不是异步的,因为它在我的示例图中是图3。 I / O调用会阻塞该线程(虽然我们感觉不到它,因为它们被并行阻止)。
但是,如果是这种情况,我很困惑为什么request-futures被认为是异步的,因为它是ThreadPoolExecutor的包装器:
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
future_to_url = {executor.submit(load_url, url, 10): url for url in get_urls()}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
这只能在一个线程上运行吗?
特别是与asyncio相比时,这意味着它可以运行单线程
只有两种方法可以在单个处理器上安装程序 “一次不止一件事。”多线程编程就是这样 最简单和最流行的方式,但还有另一种方式 不同的技术,让你拥有几乎所有的优点 多线程,没有实际使用多个线程。是真的 只有当你的程序主要受I / O限制时才有用。如果你的程序 处理器绑定,然后可能是先发制人的预定线程 你真正需要的是什么网络服务器很少受处理器限制, 但是。
答案 0 :(得分:4)
首先,一个注释:concurrent.futures.Future
与asyncio.Future
不同。基本上它只是一个抽象 - 一个对象,它允许您在分配作业之后但在它完成之前在程序中引用作业结果(或异常,这也是结果)。它类似于将常用函数的结果分配给某个变量。
多线程:关于您的示例,当使用多个线程时,您可以说您的代码是"异步"因为几个操作同时在不同的线程中执行而不等待彼此完成,您可以在时序结果中看到它。而你是对的,由于sleep
导致你的函数被阻塞,它会在指定的时间内阻塞工作线程,但是当你使用多个线程时,这些线程会被并行阻塞。因此,如果您有一个sleep
的作业而另一个没有并运行多个线程,那么没有sleep
的那个将执行计算,而另一个将睡眠。当你使用单线程时,作业会一个接一个地以连续方式执行,所以当一个作业休眠时,其他作业等待它,实际上它们只是不存在直到它们轮到它们。所有这一切都通过您的时间测试证明了。 print
发生的事情与"线程安全"有关,即print使用标准输出,这是一个共享资源。因此,当您的多个线程同时尝试打印时,内部发生切换并且您获得了奇怪的输出。 (这也显示了多线程示例中的" asynchronicity")为了防止这种错误,存在锁定机制,例如:锁,信号量等。
Asyncio :为了更好地理解注意" IO"部分,它不是“异步计算”,而是“异步输入/输出' 。在谈论asyncio时,你通常不会考虑线程。 Asyncio是关于事件循环和生成器(协同程序)。事件循环是仲裁器,它控制注册到循环的协程(及其回调)的执行。协同程序实现为生成器,即允许迭代执行某些操作的函数,在每次迭代时保存状态并返回'以及在下一次调用时继续保存状态。所以基本上事件循环是while True:
循环,它会一个接一个地调用分配给它的所有协同程序/生成器,并且它们在每个这样的调用上提供结果或无结果 - 这为"异步性提供了可能性#34 ;. (简化,作为调度机制,优化此行为。)在这种情况下的事件循环可以在单线程中运行,如果协同程序是非阻塞的,它将给你真正的"异步性" ,但如果它们被阻止,那么它基本上是线性执行。
您可以使用显式多线程实现相同的功能,但线程成本高昂 - 它们需要分配内存,切换它们需要时间等等。另一方面,asyncio API允许您从实际实现中抽象出来并考虑您的工作异步执行。它的实现可能不同,它包括调用OS API,OS决定做什么,例如, DMA,附加线程,某些特定的微控制器使用等等。由于较低级别的机制,硬件原因,它适用于IO。另一方面,执行计算将需要将计算算法明确地分解为片段以用作asyncio协程,因此单独的线程可能是更好的决策,因为您可以将整个计算作为一个启动。 (我不是在谈论并行计算特有的算法)。但是asyncio事件循环可能被显式设置为使用单独的线程来进行协同程序,所以这将是asyncio与多线程。
关于你的例子,如果你用sleep
作为asyncio coroutine来实现你的函数,并且运行其中50个单线程,你将获得类似于第一次测试的时间,即在25s
附近,因为它正在阻挡。如果你将它更改为类似yield from [asyncio.sleep][3](0.5)
(这是一个协程本身)的东西,则将其中50个单线程运行,它将被异步调用。因此,当一个协同程序将睡眠时,另一个协同程序将启动,依此类推。作业将及时完成,类似于第二个多线程测试,即接近0.5s
。如果你在这里添加print
,你将获得良好的输出,因为单线程将以串行方式使用它,但是输出可能与循环的协程分配的顺序不同,如协程可以按不同的顺序运行。如果你将使用多个线程,那么结果显然会接近最后一个。
简化:multythreading和asyncio的区别在于阻塞/非阻塞,所以基本上阻塞多线程会接近非阻塞asyncio ,但是&# 39;有很多不同之处。
关于你的原始陈述:
- 所有异步代码都是多线程的
- 所有多线程函数都是异步的
我希望我能够表明:
- 异步代码可能是单线程和多线程的
- 可以调用所有多线程函数"异步"
答案 1 :(得分:0)
我认为主要的混淆来自异步的含义。来自免费在线计算词典,"一个可以独立进行的流程[...]是异步的。现在,将其应用于蜜蜂所做的事情:
print()
。虽然调用是独立的,但在某些时候,数据会汇集到同一输出目标中,并且此时会强制执行序列。我不会称之为异步。但请注意,print()
的两个参数以及尾随换行符是独立处理的,这就是它们可以交错的原因。q.join()
。当然,调用线程在队列为空之前被阻塞,因此需要强制执行某种同步。我不明白为什么这个"似乎打破"为你。