Python Language
Asyncio modul
Sök…
Coroutine och delegationssyntax
Innan Python 3.5+ släpptes asyncio
modulen generatorer för att härma asynkrona samtal och hade därmed en annan syntax än den nuvarande Python 3.5-utgåvan.
Python 3.5 introducerade async
och await
nyckelord. Observera avsaknaden av parenteser runt det await func()
samtalet.
import asyncio
async def main():
print(await func())
async def func():
# Do time intensive stuff...
return "Hello, world!"
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Före Python 3.5 användes @asyncio.coroutine
dekoratören för att definiera en koroutin. Utbytet från uttrycket användes för generatordelegering. Notera parenteserna kring yield from func()
.
import asyncio
@asyncio.coroutine
def main():
print((yield from func()))
@asyncio.coroutine
def func():
# Do time intensive stuff..
return "Hello, world!"
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Här är ett exempel som visar hur två funktioner kan köras asynkront:
import asyncio
async def cor1():
print("cor1 start")
for i in range(10):
await asyncio.sleep(1.5)
print("cor1", i)
async def cor2():
print("cor2 start")
for i in range(15):
await asyncio.sleep(1)
print("cor2", i)
loop = asyncio.get_event_loop()
cors = asyncio.wait([cor1(), cor2()])
loop.run_until_complete(cors)
Asynkrona körare
Obs: Använder syntaks Python 3.5+ async / invänta
asyncio
stöder användningen av Executor
objekt som finns i concurrent.futures
för att schemalägga uppgifter asynkront. Event-loopar har funktionen run_in_executor()
som tar ett Executor
objekt, en Callable
och Callable-parametrarna.
Schemalägga en uppgift för en Executor
import asyncio
from concurrent.futures import ThreadPoolExecutor
def func(a, b):
# Do time intensive stuff...
return a + b
async def main(loop):
executor = ThreadPoolExecutor()
result = await loop.run_in_executor(executor, func, "Hello,", " world!")
print(result)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
Varje händelsslinga har också en "standard" Executor
plats som kan tilldelas en Executor
. För att tilldela en Executor
och schemalägga uppgifter från slingan använder set_default_executor()
metoden set_default_executor()
.
import asyncio
from concurrent.futures import ThreadPoolExecutor
def func(a, b):
# Do time intensive stuff...
return a + b
async def main(loop):
# NOTE: Using `None` as the first parameter designates the `default` Executor.
result = await loop.run_in_executor(None, func, "Hello,", " world!")
print(result)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.set_default_executor(ThreadPoolExecutor())
loop.run_until_complete(main(loop))
Det finns två huvudtyper av Executor
i concurrent.futures
, ThreadPoolExecutor
och ProcessPoolExecutor
. ThreadPoolExecutor
innehåller en pool av trådar som antingen kan ställas in manuellt på ett specifikt antal trådar genom konstruktören eller som standard är antalet kärnor på maskintiderna. ThreadPoolExecutor
använder poolen av trådar för att utföra uppgifter som tilldelats den och är i allmänhet bättre vid CPU-bundna operationer snarare än I / O-bundna operationer. Kontrast det till ProcessPoolExecutor
som skapar en ny process för varje uppgift som tilldelats den. ProcessPoolExecutor
kan bara ta uppgifter och parametrar som är plockbara. De vanligaste icke-plockbara uppgifterna är föremålens metoder. Om du måste schemalägga ett objekts metod som en uppgift i en Executor
måste du använda en ThreadPoolExecutor
.
Använda UVLoop
uvloop
är en implementering för asyncio.AbstractEventLoop
baserat på libuv (Används av nodejs). Den överensstämmer med 99% av asyncio
funktionerna och är mycket snabbare än den traditionella asyncio.EventLoop
. uvloop
är för närvarande inte tillgängligt på Windows, installera det med pip install uvloop
.
import asyncio
import uvloop
if __name__ == "__main__":
asyncio.set_event_loop(uvloop.new_event_loop())
# Do your stuff here ...
Man kan också ändra EventLoopPolicy
genom att ställa EventLoopPolicy
till den i uvloop
.
import asyncio
import uvloop
if __name__ == "__main__":
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.new_event_loop()
Synkronisering Primitiv: Händelse
Begrepp
Använd en Event
att synkronisera schemaläggningen för flera koroutiner .
Enkelt uttryckt är att en händelse är som pistolskottet i en löpning: den låter löparna utanför startblocken.
Exempel
import asyncio
# event trigger function
def trigger(event):
print('EVENT SET')
event.set() # wake up coroutines waiting
# event consumers
async def consumer_a(event):
consumer_name = 'Consumer A'
print('{} waiting'.format(consumer_name))
await event.wait()
print('{} triggered'.format(consumer_name))
async def consumer_b(event):
consumer_name = 'Consumer B'
print('{} waiting'.format(consumer_name))
await event.wait()
print('{} triggered'.format(consumer_name))
# event
event = asyncio.Event()
# wrap coroutines in one future
main_future = asyncio.wait([consumer_a(event),
consumer_b(event)])
# event loop
event_loop = asyncio.get_event_loop()
event_loop.call_later(0.1, functools.partial(trigger, event)) # trigger event in 0.1 sec
# complete main_future
done, pending = event_loop.run_until_complete(main_future)
Produktion:
Konsument B väntar
Konsument A väntar
EVENT SET
Konsument B utlöses
Konsument A utlöses
En enkel Websocket
Här skapar vi ett enkelt eko-nätuttag med asyncio
. Vi definierar koroutiner för att ansluta till en server och skicka / ta emot meddelanden. Kommuniceringarna av webbuttaget körs i en main
coroutine, som drivs av en händelsslinga. Detta exempel modifieras från ett tidigare inlägg .
import asyncio
import aiohttp
session = aiohttp.ClientSession() # handles the context manager
class EchoWebsocket:
async def connect(self):
self.websocket = await session.ws_connect("wss://echo.websocket.org")
async def send(self, message):
self.websocket.send_str(message)
async def receive(self):
result = (await self.websocket.receive())
return result.data
async def main():
echo = EchoWebsocket()
await echo.connect()
await echo.send("Hello World!")
print(await echo.receive()) # "Hello World!"
if __name__ == '__main__':
# The main loop
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Vanlig missuppfattning om asyncio
förmodligen den vanligaste missuppfattningen om asnycio
är att den låter dig köra vilken som helst uppgift parallellt - att ta bort GIL (global tolklås) och därför utföra blockeringsjobb parallellt (på separata trådar). det gör det inte !
asyncio
(och bibliotek som är byggda för att samarbeta med asyncio
) bygger på koroutiner: funktioner som (i samarbete) ger kontrollflödet tillbaka till den uppringande funktionen. notera asyncio.sleep
i exemplen ovan. detta är ett exempel på en icke-blockerande koroutin som väntar "i bakgrunden" och ger kontrollflödet tillbaka till den samtalande funktionen (när den kallas med await
). time.sleep
är ett exempel på en blockeringsfunktion. programmets körningsflöde stannar bara där och kommer bara tillbaka efter att time.sleep
har avslutats.
ett verkligt exempel är requests
som består (för tillfället) endast på blockeringsfunktioner. det finns ingen samtidighet om du kallar någon av dess funktioner inom asyncio
. aiohttp
å andra sidan byggdes med asyncio
i åtanke. dess koroutiner kommer att köras samtidigt.
om du har långvariga CPU-bundna uppgifter vill du köra parallellt
asyncio
inte är för dig. för det behöver duthreads
ellermultiprocessing
.Om du har IO-bundna jobb som kör, kan du köra dem samtidigt med hjälp av
asyncio
.