Det finns flera sätt att effektivt utföra en pythonkörslinga parallellt, beroende på vilken typ av uppgift som utförs i slingan och de tillgängliga resurserna. Här är en uppdelning av vanliga tillvägagångssätt och deras överväganden:
1. Multiprocessing (CPU-bundna uppgifter):
- När man ska använda: Idealisk för uppgifter som är beräkningsintensiva (CPU-bundna), såsom antal crunching, bildbehandling eller komplexa beräkningar. Dessa uppgifter drar mest nytta av att använda flera kärnor.
- Hur det fungerar: Skapar separata processer, var och en med sitt eget minnesutrymme. Detta undviker de globala tolklåsbegränsningarna (GIL), vilket möjliggör verklig parallell exekvering.
- Exempel:
`` `python
importera multiprocessing
importtid
DEF Process_Item (artikel):
"" "Simulerar en CPU-bunden uppgift." "" "
Time.Sleep (1) # Simulera arbetet
returnera artikel * 2
def main ():
objekt =lista (intervall (10))
start_time =time.time ()
med multiprocessing.pool (processer =multiprocessing.cpu_count ()) som pool:
resultat =pool.map (process_item, objekt)
end_time =tid.time ()
utskrift (F "Resultat:{Resultat}")
utskrift (f "Time Take:{END_TIME - START_TIME:.2F} sekunder")
om __name__ =="__main__":
Main ()
`` `
- Förklaring:
- `multiprocessing.pool`:Skapar en pool av arbetarprocesser. `multiprocessing.cpu_count ()` använder automatiskt antalet tillgängliga kärnor.
- `Pool.map`:Tillämpar funktionen 'Process_Item' på varje objekt i listan` artiklar 'och distribuerar arbetet över arbetarprocesserna. Det hanterar automatiskt att dela upp arbetet och samla in resultaten.
- `Pool.Apply_Async`:Ett icke-blockerande alternativ till` Pool.map`. Du måste samla in resultat med `result.get ()` för varje objekt.
- `pool.imap` och` pool.imap_unordered`:iterators som returnerar resultat när de blir tillgängliga. `IMAP_Unordered 'garanterar inte resultaten.
- `Pool.starmap`:Liknar` Pool.map`, men låter dig skicka flera argument till arbetarfunktionen med tuples.
- Fördelar:
- Övergår GIL-begränsningen för CPU-bundna uppgifter.
- använder flera CPU -kärnor effektivt.
- nackdelar:
- Högre overhead än gängning på grund av att skapa separata processer.
- Kommunikation mellan processer (förbipasserande data) kan vara långsammare.
- Mer minnesintensivt, eftersom varje process har sitt eget minnesutrymme.
- Kan vara mer komplex för att hantera delat tillstånd (behöver kommunikationsmekanismer mellan processer som köer eller delat minne).
2. Gängning (I/O-bundna uppgifter):
- När man ska använda: Lämplig för uppgifter som spenderar en betydande tid på att vänta på externa operationer (I/O-bundna), till exempel nätverksförfrågningar, diskläsningar/skrivningar eller databasfrågor.
- Hur det fungerar: Skapar flera trådar inom en enda process. Trådar delar samma minnesutrymme. GIL (global tolklås) begränsar verklig parallellism i CPython, men trådar kan fortfarande förbättra prestandan genom att släppa GIL när du väntar på I/O.
- Exempel:
`` `python
importtrådning
importtid
DEF FETCH_URL (URL):
"" "Simulerar en I/O-bunden uppgift." ""
tryck (f "hämtning {url}")
Time.Sleep (2) # Simulera nätverksfördröjning
tryck (f "färdig hämtning {url}")
return f "innehåll i {url}"
def main ():
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
trådar =[]
resultat =[]
För URL i URL:er:
tråd =tråd.
trådar.Append (tråd)
tråd.start ()
För tråd i trådar:
tråd.join () # vänta tills alla trådar ska slutföras
end_time =tid.time ()
utskrift (F "Resultat:{Resultat}")
utskrift (f "Time Take:{END_TIME - START_TIME:.2F} sekunder")
om __name__ =="__main__":
Main ()
`` `
- Förklaring:
- `Threading.Thread ':Skapar en ny tråd.
- `Thread.Start ()`:Starta trådens körning.
- `Thread.Join ()`:Väntar på att tråden ska vara klar.
- lambda -funktion: Används för att passera "url" som ett argument för att "hämta_url" inom "tråd" -konstruktören. Det är viktigt att passera "url" * med värde * för att undvika rasförhållanden där alla trådar kan hamna med det sista värdet på "url".
- Fördelar:
- lägre omkostnader än multiprocessing.
- Delar minnesutrymme, vilket gör det lättare att dela data mellan trådar (men kräver noggrann synkronisering).
- Kan förbättra prestanda för I/O-bundna uppgifter trots GIL.
- nackdelar:
- GIL begränsar verklig parallellism för CPU-bundna uppgifter i CPython.
- Kräver noggrann synkronisering (lås, semaforer) för att förhindra rasförhållanden och datakorruption vid åtkomst till delade resurser.
3. Asyncio (samtidighet med en enda tråd):
- När man ska använda: Utmärkt för att hantera ett stort antal I/O-bundna uppgifter samtidigt inom en enda tråd. Ger ett sätt att skriva asynkron kod som kan växla mellan uppgifter medan du väntar på att I/O -operationer ska slutföras.
- Hur det fungerar: Använder en händelsesslinga för att hantera koroutiner (specialfunktioner deklarerade med `async def '). Koroutiner kan avbryta sin exekvering medan de väntar på I/O och låta andra koroutiner köra. `Asyncio` ger * inte * verklig parallellism (det är samtidighet), men det kan vara mycket effektivt för I/O-bundna operationer.
- Exempel:
`` `python
importera asyncio
importtid
Async def fetch_url (url):
"" "Simulerar en I/O-bunden uppgift (asynkron)." "" "
tryck (f "hämtning {url}")
Vänta asyncio.sleep (2) # Simulera nätverksfördröjning (icke-blockerande)
tryck (f "färdig hämtning {url}")
return f "innehåll i {url}"
async def main ():
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
Uppgifter =[FETCH_URL (URL) för URL i URL:er]
Resultat =vänta på Asyncio.Gather (*uppgifter) # Köruppgifter samtidigt
end_time =tid.time ()
utskrift (F "Resultat:{Resultat}")
utskrift (f "Time Take:{END_TIME - START_TIME:.2F} sekunder")
om __name__ =="__main__":
asyncio.run (main ())
`` `
- Förklaring:
- `Async def`:Definierar en asynkron funktion (koroutin).
- `Vänta ':avbryta genomförandet av koroutinet tills den väntade operationen är klar. Den släpper kontrollen till händelsesslingan, vilket gör att andra koroutiner kan köra.
- `Asyncio.sleep ':En asynkron version av` Time.Sleep' som inte blockerar händelsesslingan.
- `Asyncio.Gather`:Kör flera koroutiner samtidigt och returnerar en lista över deras resultat i den ordning de lämnades in. `*Uppgifter packar upp listan över uppgifter.
- `Asyncio.Run`:Starta Asyncio -händelsesslingan och kör" Main "Coroutine.
- Fördelar:
- Mycket effektiv för I/O-bundna uppgifter, även med en enda tråd.
- undviker omkostnaderna för att skapa flera processer eller trådar.
- lättare att hantera samtidighet än gängning (mindre behov av uttryckliga lås).
- Utmärkt för att bygga mycket skalbara nätverksapplikationer.
- nackdelar:
- kräver att man använder asynkrona bibliotek och kod, vilket kan vara mer komplicerat att lära sig och felsöka än synkron kod.
- Inte lämpligt för CPU-bundna uppgifter (ger inte verklig parallellism).
- förlitar sig på asynkrona-kompatibla bibliotek (t.ex. `aiohttp` istället för" förfrågningar ").
4. Samtidiga.Futurer (abstraktion över multiprocessing och gängning):
- När man ska använda: Tillhandahåller ett gränssnitt på hög nivå för att utföra uppgifter asynkront med antingen trådar eller processer. Låter dig att växla mellan gängningar och multiprocesser utan att avsevärt ändra din kod.
- Hur det fungerar: Använder "ThreadPoolExecutor" för gängning och "ProcessPoolExecutor" för multiprocessing.
- Exempel:
`` `python
importera samtidiga.
importtid
DEF Process_Item (artikel):
"" "Simulerar en CPU-bunden uppgift." "" "
Time.Sleep (1) # Simulera arbetet
returnera artikel * 2
def main ():
objekt =lista (intervall (10))
start_time =time.time ()
med samtidig.futures.processpoolexecutor (max_workers =multiprocessing.cpu_count ()) som exekutiv:
# Skicka varje objekt till exekutorn
futures =[executor.submit (process_item, objekt) för objekt i objekt]
# Vänta på att alla framtider slutförs och får resultaten
resultat =[Future.Result () för framtiden i samtidiga.futures.as_completed (futures)]
end_time =tid.time ()
utskrift (F "Resultat:{Resultat}")
utskrift (f "Time Take:{END_TIME - START_TIME:.2F} sekunder")
om __name__ =="__main__":
importera multiprocessing
Main ()
`` `
- Förklaring:
- `Samtidig.futures.processpoolexecutor`:Skapar en pool av arbetarprocesser. Du kan också använda `samtidiga.futures.ThreadPoolExecutor` för trådar.
- `Executor.Submit`:skickar in en kallbar (funktion) till exekutorn för asynkron exekvering. Returnerar ett "framtida" objekt som representerar resultatet av exekveringen.
- `Samtidig.futures.as_completed`:En iterator som ger" framtida "objekt när de slutför, i ingen särskild ordning.
- `Future.Result ()`:Hämtar resultatet av den asynkrona beräkningen. Det kommer att blockera tills resultatet är tillgängligt.
- Fördelar:
- Högnivågränssnitt, förenklande asynkron programmering.
- Växla enkelt mellan trådar och processer genom att ändra exekutörstypen.
- ger ett bekvämt sätt att hantera asynkrona uppgifter och hämta sina resultat.
- nackdelar:
- kan ha något mer overhead än att använda "multiprocessing" eller "gängning" direkt.
Att välja rätt tillvägagångssätt:
| Tillvägagångssätt | Uppgiftstyp | GIL -begränsning | Minnesanvändning | Komplexitet |
| ------------------- | ------------------- | ---------------- | --------------- | ------------ |
| Multiprocessing | CPU-bundet | Övervinna | Hög | Måttlig |
| Gängande | I/O-bundet | Ja | Låg | Måttlig |
| Asyncio | I/O-bundet | Ja | Låg | Hög |
| Samtidiga.Futurer | Båda | Beror | Varierar | Låg |
Nyckelöverväganden:
* Uppgiftstyp (CPU-bundet kontra I/O-bundet): Detta är den viktigaste faktorn. CPU-bundna uppgifter drar nytta av multiprocessing, medan I/O-bundna uppgifter är bättre lämpade för gängning eller asyncio.
* Gil (Global Interpreter Lock): GIL i CPYTHON begränsar sann parallellism vid gängning. Om du behöver verklig parallellism för CPU-bundna uppgifter, använd multiprocessing.
* overhead: Multiprocessing har högre omkostnader än gängning och asyncio.
* Minnesanvändning: Multiprocessing använder mer minne eftersom varje process har sitt eget minnesutrymme.
* Komplexitet: Asyncio kan vara mer komplex att lära sig än gängning eller multiprocessing.
* Datadelning: Att dela data mellan processer (multiprocessing) kräver kommunikationsmekanismer mellan processer (köer, delat minne), vilket kan lägga till komplexitet. Trådar delar minnesutrymme, men kräver noggrann synkronisering för att undvika rasförhållanden.
* biblioteksstöd: Se till att biblioteken du använder är kompatibla med Asyncio om du väljer den metoden. Många bibliotek erbjuder nu asynkrona versioner (t.ex. `AIOHTTP` för HTTP -förfrågningar).
bästa metoder:
* Profil din kod: Innan du implementerar parallellism profilerar du din kod för att identifiera flaskhalsarna. Optimera inte för tidigt.
* Mätprestanda: Testa olika tillvägagångssätt och mät deras prestanda för att avgöra vilken som fungerar bäst för ditt specifika användningsfall.
* Håll uppgifterna oberoende: Ju mer oberoende dina uppgifter är, desto lättare blir det att parallellisera dem.
* Hantera undantag: Hanterar korrekt undantag i dina arbetarfunktioner eller koroutiner för att förhindra att de kraschar hela applikationen.
* Använd köer för kommunikation: Om du behöver kommunicera mellan processer eller trådar använder du kö för att undvika rasförhållanden och säkerställa trådsäkerhet.
* Överväg en meddelandekö: För komplexa, distribuerade system kan du överväga att använda en meddelandekö (t.ex. RabbitMQ, Kafka) för asynkron uppgiftsbehandling.
Genom att noggrant överväga dessa faktorer kan du välja det mest effektiva tillvägagångssättet för att utföra din Python Run -slinga parallellt och förbättra prestandan för din applikation. Kom ihåg att testa och mäta resultaten för att säkerställa att din valda tillvägagångssätt faktiskt ger en prestationsfördel.