2015-02-06 52 views
10

我想熟悉asyncio,所以我決定編寫一個數據庫客戶端。但是,性能完全匹配同步代碼。我相信這是我對一個概念的誤解。有人能解釋我在做什麼?asyncio的性能

請參見例如下面的代碼:提前

class Connection: 
    def __init__(self, reader, writer, loop): 
     self.futures = deque() 

     # ... 

     self.reader_task = asyncio.async(self.recv_data(), loop=self.loop) 

    @asyncio.coroutine 
    def recv_data(self): 
     while 1: 
      try: 
       response = yield from self.reader.readexactly(4) 
       size, = struct.unpack('I', response) 
       response = yield from self.reader.readexactly(size) 

       # ...     

       future = self.futures.popleft() 

       if not future.cancelled(): 
        future.set_result(response) 

      except Exception: 
       break 

    def send_data(self, data): 
     future = asyncio.Future(loop=self.loop) 
     self.futures.append(future) 

     self.writer.write(data) 

     return future 


loop = asyncio.get_event_loop() 


@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 

    for i in range(10000): 
     yield from connection.send_data(...) 


s = time.monotonic() 

loop.run_until_complete(benchmark()) 

e = time.monotonic() 
print('Requests per second:', int(10000/(e - s))) 

感謝。

回答

12

你在撥打電話send_data時出錯了。現在,你有這樣的:

@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 

    for i in range(10000): 
     yield from connection.send_data(...) 

通過使用yield from裏面的for循環,你就等着future你從send_data返回上移動到下一個呼叫之前產生結果。這使得你的程序基本同步。你想給send_data您的來電,並然後等待結果:

@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 
    yield from asyncio.wait([connection.send_data(..) for _ in range(10000)]) 
+0

完美,謝謝。從我可以理解的每一個「send_data」調用創建一個任務相同? – Andrew 2015-02-06 19:23:46

+2

@Andrew或多或少,雖然你仍然需要添加代碼到'benchmark'來等待每個'Task'完成。實際上,我認爲調用'asyncio.wait'會將所有傳遞給它的協程對象內部轉換爲'Task'實例。 – dano 2015-02-06 19:40:41

+0

是的,你們都是對的。 'asyncio.wait'將包裝任何傳入協程對象或在未來的任務中等待。使用'loop.create_task'或'asyncio.ensure_future'自己封裝它們的行爲可能會在循環中安排它們,但在最終完成時不阻止執行協程代碼。你仍然必須從那些Task中產生出來,或者把它們傳遞給'asyncio.wait'。 – 2016-10-01 06:07:19

3

蟒蛇asyncio模塊是單線程:

該模塊提供基礎設施編寫使用協程單線程併發代碼,複用通過套接字和其他資源的I/O訪問,運行網絡客戶端和服務器,以及其他相關的基元。

This question有爲什麼ASYNCIO可以比線程更慢的解釋,但在短:ASYNCIO使用單個線程來執行你的代碼,所以即使你有多個協程,他們都串行執行。線程池用於執行一些回調和I/O。由於GIL,線程還可以串行執行用戶代碼,儘管I/O操作可以同步運行。

使用asyncio的原因並不能改善您對串行執行代碼的改進,因爲事件循環一次只運行一個協程。

+5

的有機磷農藥的代碼應該仍然比同步代碼執行得更好,因爲它是I/O瓶頸。沒有關係,只有一個線程 - 當I/O在一個協程中運行時,其他協程可以執行。你鏈接到的問題是一個有點特殊的情況 - 它使用'getaddrinfo',這實際上並不是使用異步I/O實現的。它使用一個小的'ThreadPool'來代替,這限制了可用的並行度。這使得它比普通的多線程代碼慢,但它仍然比同步代碼更快,這就是這個問題的關鍵。 – dano 2015-02-06 17:04:13

+1

@dano我的錯誤。我不太瞭解它。我正在對您的答案進行投票。 – zstewart 2015-02-06 17:05:34

+1

沒問題。異步框架是一個相當奇怪的概念來包裝你的頭。你在回答中寫的最後一句話其實基本上是正確的,但是這是因爲編碼錯誤而發生的,而不是'asyncio'的限制。 – dano 2015-02-06 17:08:50