这个多线程函数是否异步

时间:2016-03-07 02:24:23

标签: python multithreading asynchronous python-asyncio

我害怕我仍然有点困惑(尽管检查其他线程)是否:

  • 所有异步代码都是多线程的
  • 所有多线程函数都是异步的

我最初的猜测是两者都没有,并且正确的异步代码应该能够在一个线程中运行 - 但是可以通过添加线程来改进它,例如:

enter image description here

所以我构建了这个玩具示例:

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限制时才有用。如果你的程序   处理器绑定,然后可能是先发制人的预定线程   你真正需要的是什么网络服务器很少受处理器限制,   但是。

2 个答案:

答案 0 :(得分:4)

首先,一个注释:concurrent.futures.Futureasyncio.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;有很多不同之处。

  • 用于计算的多线程(即CPU绑定代码)
  • 用于输入/输出的Asyncio(即I / O绑定代码)

关于你的原始陈述:

  
      
  • 所有异步代码都是多线程的
  •   
  • 所有多线程函数都是异步的
  •   

我希望我能够表明:

  
      
  • 异步代码可能是单线程和多线程的
  •   
  • 可以调用所有多线程函数"异步"
  •   

答案 1 :(得分:0)

我认为主要的混淆来自异步的含义。来自免费在线计算词典,"一个可以独立进行的流程[...]是异步的。现在,将其应用于蜜蜂所做的事情:

  • 从队列中检索项目。一次只能有一个这样做,而他们获得项目的顺序是不确定的。我不会称之为异步。
  • 睡眠。每只蜜蜂独立于所有其他蜜蜂这样做,即睡眠持续时间全部运行,否则多个蜜蜂的时间不会下降。我称之为异步。
  • 致电print()。虽然调用是独立的,但在某些时候,数据会汇集到同一输出目标中,并且此时会强制执行序列。我不会称之为异步。但请注意,print()的两个参数以及尾随换行符是独立处理的,这就是它们可以交错的原因。
  • 最后,致电q.join()。当然,调用线程在队列为空之前被阻塞,因此需要强制执行某种同步。我不明白为什么这个"似乎打破"为你。