2016-03-07 71 views
3

恐怕我還是有點困惑(儘管檢查其它線程)是否:這是多線程功能異步

  • 所有異步代碼是多線程
  • 所有的多線程功能是異步

我最初的猜測是沒有這兩個和適當的異步代碼應該能夠在一個線程中運行 - 但它可以通過增加線程例如,像這樣進行改進:

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調用阻塞了線程(儘管我們並不覺得它是因爲它們被並行阻塞)。

但是,如果這是我很困惑,爲什麼要求期貨被認爲是異步的,因爲它是周圍的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

我覺得主要的混淆來自異步的含義。從免費在線電腦字典中,「一個獨立執行的進程」是異步的。現在,將它應用到你的蜜蜂所做的事情上:

  • 從隊列中檢索一個項目。一次只能有一個可以做到這一點,而他們獲取物品的順序是未定義的。我不會稱之爲異步。
  • 睡覺。每隻蜜蜂都獨立於所有其他蜜蜂,即睡眠持續時間在所有蜜蜂上運行,否則時間不會隨着多隻蜜蜂而下降。我會稱之爲異步。
  • 致電print()。雖然這些調用是獨立的,但在某些時候,數據會彙集到相同的輸出目標中,並且在那一點上會執行一個序列。我不會稱之爲異步。但請注意,print()的兩個參數以及尾隨的換行符都是獨立處理的,這就是它們可以交錯的原因。
  • 最後,致電q.join()。當然,調用線程會被阻塞,直到隊列爲空,因此需要執行某種同步。我不明白爲什麼這個「似乎爲你打破」。
3

Firt的全部,一個音符concurrent.futures.Futureasyncio.Future不一樣。基本上它只是一個抽象 - 一個對象,它允許您在分配作業之後但在完成之前在程序中引用作業結果(或異常,這也是結果)。這與將通用函數的結果分配給某個變量類似。

多線程:關於你提到的例子中,使用多線程的時候,你可以說,你的代碼是「異步」的幾個操作在同一時間diffeent線程無需等待對方完成執行,並且可以看到它在計時結果。你說得對,由於sleep的功能是阻塞的,它在指定的時間內阻塞了工作線程,但是當你使用多個線程時,那些線程被並行阻塞。因此,如果您有一個作業使用sleep,而另一個作業沒有並運行多個線程,則沒有sleep的作業將執行計算,而另一個作業將進入休眠狀態。當你使用單線程時,這些工作是以串行方式一個接一個地執行的,所以當一個工作休眠時其他工作等待它,實際上只是不存在,直到它轉向。所有這些都通過您的時間測試得到了很好的證明。 print與「線程安全」有關,即打印使用標準輸出,這是一個單獨的共享資源。所以當你的多線程試圖在同一時間內進行切換時,你會得到奇怪的輸出。 (這也顯示了你的多線程例子的「異步性」。)爲了防止這種錯誤,有鎖定機制,例如,鎖,信號量等

ASYNCIO:爲了更好地理解的目的注意「IO」的一部分,它不是「異步計算」,而是「異步輸入/輸出」。在談論asyncio時,你通常首先不會考慮線程。 Asyncio是關於事件循環和生成器(協程)的。事件循環是仲裁器,用於管理註冊到循環的協程(及其回調)的執行。協程被實現爲生成器,即允許迭代執行某些操作的函數,在每次迭代和「返回」時保存狀態,並在下一次調用中繼續保存狀態。所以基本上,事件循環是while True:循環,它將一個接一個地調用所有分配給它的協程/生成器,並且它們在每次這樣的調用中提供結果或無結果 - 這爲「異步性」提供了可能性。 (簡化,因爲有調度機制,可以優化這種行爲。)這種情況下的事件循環可以在單線程中運行,如果協程是非阻塞的,它會給你真正的「異步」,但是如果它們被阻塞,那麼它基本上是線性執行。

你可以通過顯式多線程實現同樣的功能,但線程代價高昂 - 它們需要分配內存,切換它們需要時間等。另一方面,asyncio API允許你從實際的實現中抽象出來,並考慮你的工作要異步執行。它的實現可能不同,它包括調用OS API並且OS決定要做什麼,例如, DMA,額外的線程,一些特定的微控制器使用等等。事情對IO來說很好,因爲低層次的機制和硬件。另一方面,執行計算需要將計算算法顯式分解爲可用作asyncio協程的peices,因此單獨的線程可能會更好,因爲您可以在整個計算中啓動整個計算。 (我不是在談論並行計算特有的算法)。但是asyncio事件循環可能被顯式設置爲使用單獨的線程進行協程,所以這將是多線程的asyncio。

關於你提到的例子,如果你會sleep實現你的功能ASYNCIO協程,shedule和運行它們的50單線程的,你會得到類似的第一次測試,即約25s時間,因爲它是阻塞。如果你將它改成類似yield from [asyncio.sleep][3](0.5)(這是一個協程本身),shedule並運行50個單線程,它將被異步調用。所以當一個協程會睡覺時另一個會開始,等等。這些工作將在時間上完成,類似於第二次多線程測試,即接近0.5s。如果您在這裏添加print,您將獲得良好的輸出,因爲它將由單線程以串行方式使用,但輸出可能與協程指定給循環的順序不同,因爲協程可能以不同的順序運行。如果你將使用多個線程,那麼結果顯然會接近最後一個線程。

簡化:在multythreading和ASYNCIO的區別在於阻塞/非阻塞,所以basicly 阻塞多線程會有所接近無阻塞ASYNCIO,但有是一個很大的分歧。

  • 多線程的計算(即CPU綁定代碼)
  • ASYNCIO輸入/輸出(即I/O密集型代碼)

關於你原來的語句:

  • 所有的異步代碼都是多線程的
  • 所有的多線程函數都是異步的

我希望能夠證明,認爲:

  • 異步代碼可能是兩個單線程和多線程
  • 所有的多線程功能,可以被稱爲「異步「
+0

@尼提卡。非常感謝這個詳細的回覆!我希望你可以看看我編輯過的第一篇文章,因爲我已經包含了一張圖表來說明爲什麼我很難看到所有多線程函數都是異步的 – mptevsion

+0

@mptevsion,你的圖很精確,只要注意一點就是, Asyncio的圖表任務可能以單線程和多線程的任何順序運行,所以它可能是Task1,Task3,Task2或任何其他類型的任務,因爲它們是先安排後執行的,這取決於實現和特定的任務(例如,它們都打開連接和任務3立即接收連接響應,而任務1和任務2仍在等待)。 – Nikita

+0

@mptevsion,關於您對多引導的擔憂,您可以通過自己表明任務1,任務2,任務3並行運行,因此彼此異步運行。想象一下,如果他們需要不同的時間來完成,那麼他們將在不同的時間點完成。如果你的關注詞在'阻塞'這個詞中,那麼就有3個線程,而1個塊 - 它只阻塞自己,其他的則完成他們的工作。在你的例子中,它們是完全相同的,這在現實生活中很少見 - 在做同樣的工作時,每個線程對於不同的輸入數據需要不同的時間。 – Nikita