Heim >Backend-Entwicklung >Python-Tutorial >Beherrschen Sie die gleichzeitige Programmierung von Python: Steigern Sie die Leistung mit erweiterten Techniken

Beherrschen Sie die gleichzeitige Programmierung von Python: Steigern Sie die Leistung mit erweiterten Techniken

Patricia Arquette
Patricia ArquetteOriginal
2024-12-13 20:39:54958Durchsuche

Mastering Python

Pythons Fähigkeiten zur gleichzeitigen Programmierung haben sich erheblich weiterentwickelt und bieten Entwicklern leistungsstarke Tools zum Schreiben von effizientem, parallelem Code. Ich habe viel Zeit damit verbracht, diese fortgeschrittenen Techniken zu erforschen, und ich freue mich, meine Erkenntnisse mit Ihnen zu teilen.

Asynchrone Programmierung mit Asyncio ist ein Game-Changer für I/O-gebundene Aufgaben. Es ermöglicht uns, nicht blockierenden Code zu schreiben, der mehrere Vorgänge gleichzeitig verarbeiten kann, ohne den Overhead des Threadings. Hier ist ein einfaches Beispiel dafür, wie wir Asyncio verwenden können, um Daten von mehreren URLs gleichzeitig abzurufen:

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = ['http://example.com', 'http://example.org', 'http://example.net']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        for url, result in zip(urls, results):
            print(f"Content length of {url}: {len(result)}")

asyncio.run(main())

Dieser Code zeigt, wie wir mehrere Coroutinen erstellen können, um Daten von verschiedenen URLs gleichzeitig abzurufen. Mit der Funktion asyncio.gather() können wir warten, bis alle Coroutinen abgeschlossen sind, und ihre Ergebnisse sammeln.

Asyncio eignet sich zwar hervorragend für I/O-gebundene Aufgaben, ist jedoch nicht für CPU-gebundene Vorgänge geeignet. Für diese wenden wir uns dem Modul concurrent.futures zu, das sowohl ThreadPoolExecutor als auch ProcessPoolExecutor bereitstellt. ThreadPoolExecutor ist ideal für I/O-gebundene Aufgaben, die die GIL nicht freigeben, während ProcessPoolExecutor perfekt für CPU-gebundene Aufgaben ist.

Hier ist ein Beispiel für die Verwendung von ThreadPoolExecutor zum gleichzeitigen Herunterladen mehrerer Dateien:

import concurrent.futures
import requests

def download_file(url):
    response = requests.get(url)
    filename = url.split('/')[-1]
    with open(filename, 'wb') as f:
        f.write(response.content)
    return f"Downloaded {filename}"

urls = [
    'https://example.com/file1.pdf',
    'https://example.com/file2.pdf',
    'https://example.com/file3.pdf'
]

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    future_to_url = {executor.submit(download_file, url): url for url in urls}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print(f"{url} generated an exception: {exc}")
        else:
            print(data)

Dieser Code erstellt einen Thread-Pool mit drei Workern und sendet eine Download-Aufgabe für jede URL. Mit der Funktion as_completed() können wir die Ergebnisse verarbeiten, sobald sie verfügbar sind, anstatt darauf zu warten, dass alle Aufgaben abgeschlossen sind.

Für CPU-gebundene Aufgaben können wir ProcessPoolExecutor verwenden, um mehrere CPU-Kerne zu nutzen. Hier ist ein Beispiel, das parallel Primzahlen berechnet:

import concurrent.futures
import math

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

def find_primes(start, end):
    return [n for n in range(start, end) if is_prime(n)]

ranges = [(1, 25000), (25001, 50000), (50001, 75000), (75001, 100000)]

with concurrent.futures.ProcessPoolExecutor() as executor:
    results = executor.map(lambda r: find_primes(*r), ranges)

all_primes = [prime for sublist in results for prime in sublist]
print(f"Found {len(all_primes)} prime numbers")

Dieser Code teilt die Aufgabe, Primzahlen zu finden, in vier Bereiche auf und verarbeitet sie parallel mithilfe separater Python-Prozesse. Die Funktion „map()“ wendet unsere Funktion „find_primes()“ auf jeden Bereich an und sammelt die Ergebnisse.

Wenn wir mit mehreren Prozessen arbeiten, müssen wir häufig Daten zwischen ihnen austauschen. Das Multiprocessing-Modul bietet hierfür mehrere Optionen, darunter Shared Memory und Queues. Hier ist ein Beispiel für die Verwendung eines Shared-Memory-Arrays:

from multiprocessing import Process, Array
import numpy as np

def worker(shared_array, start, end):
    for i in range(start, end):
        shared_array[i] = i * i

if __name__ == '__main__':
    size = 10000000
    shared_array = Array('d', size)

    # Create 4 processes
    processes = []
    chunk_size = size // 4
    for i in range(4):
        start = i * chunk_size
        end = start + chunk_size if i < 3 else size
        p = Process(target=worker, args=(shared_array, start, end))
        processes.append(p)
        p.start()

    # Wait for all processes to finish
    for p in processes:
        p.join()

    # Convert shared array to numpy array for easy manipulation
    np_array = np.frombuffer(shared_array.get_obj())
    print(f"Sum of squares: {np_array.sum()}")

Dieser Code erstellt ein gemeinsam genutztes Speicherarray und verwendet vier Prozesse, um die Quadrate von Zahlen parallel zu berechnen. Das gemeinsam genutzte Array ermöglicht allen Prozessen das Schreiben in denselben Speicherbereich, sodass keine Kommunikation zwischen Prozessen erforderlich ist.

Obwohl diese Techniken wirkungsvoll sind, bringen sie auch ihre eigenen Herausforderungen mit sich. Race-Bedingungen, Deadlocks und übermäßiger Kontextwechsel können sich alle auf die Leistung und Korrektheit auswirken. Es ist wichtig, Ihren gleichzeitigen Code sorgfältig zu entwerfen und bei Bedarf geeignete Synchronisierungsprimitive zu verwenden.

Wenn beispielsweise mehrere Threads oder Prozesse auf eine gemeinsam genutzte Ressource zugreifen müssen, können wir eine Sperre verwenden, um die Thread-Sicherheit zu gewährleisten:

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = ['http://example.com', 'http://example.org', 'http://example.net']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        for url, result in zip(urls, results):
            print(f"Content length of {url}: {len(result)}")

asyncio.run(main())

Dieser Code zeigt, wie eine Sperre verwendet wird, um einen gemeinsam genutzten Zähler vor Race-Conditions zu schützen, wenn ihn mehrere Threads gleichzeitig erhöhen.

Eine weitere fortschrittliche Technik ist die Verwendung von Semaphoren zur Steuerung des Zugriffs auf eine begrenzte Ressource. Hier ist ein Beispiel, das die Anzahl gleichzeitiger Netzwerkverbindungen begrenzt:

import concurrent.futures
import requests

def download_file(url):
    response = requests.get(url)
    filename = url.split('/')[-1]
    with open(filename, 'wb') as f:
        f.write(response.content)
    return f"Downloaded {filename}"

urls = [
    'https://example.com/file1.pdf',
    'https://example.com/file2.pdf',
    'https://example.com/file3.pdf'
]

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    future_to_url = {executor.submit(download_file, url): url for url in urls}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print(f"{url} generated an exception: {exc}")
        else:
            print(data)

Dieser Code verwendet ein Semaphor, um die Anzahl gleichzeitiger Netzwerkverbindungen auf 10 zu begrenzen und so eine Überlastung des Netzwerks oder des Servers zu verhindern.

Bei der Arbeit mit gleichzeitigem Code ist es auch wichtig, Ausnahmen richtig zu behandeln. Das Asyncio-Modul stellt der Funktion asyncio.gather() einen return_Exceptions-Parameter zur Verfügung, der hierfür nützlich sein kann:

import concurrent.futures
import math

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

def find_primes(start, end):
    return [n for n in range(start, end) if is_prime(n)]

ranges = [(1, 25000), (25001, 50000), (50001, 75000), (75001, 100000)]

with concurrent.futures.ProcessPoolExecutor() as executor:
    results = executor.map(lambda r: find_primes(*r), ranges)

all_primes = [prime for sublist in results for prime in sublist]
print(f"Found {len(all_primes)} prime numbers")

Dieser Code zeigt, wie Ausnahmen in gleichzeitigen Aufgaben behandelt werden, ohne die Ausführung anderer Aufgaben zu stoppen.

Wenn wir tiefer in die gleichzeitige Programmierung eintauchen, stoßen wir auf fortgeschrittenere Konzepte wie Ereignisschleifen und Coroutine-Verkettung. Hier ist ein Beispiel, das zeigt, wie Coroutinen verkettet werden:

from multiprocessing import Process, Array
import numpy as np

def worker(shared_array, start, end):
    for i in range(start, end):
        shared_array[i] = i * i

if __name__ == '__main__':
    size = 10000000
    shared_array = Array('d', size)

    # Create 4 processes
    processes = []
    chunk_size = size // 4
    for i in range(4):
        start = i * chunk_size
        end = start + chunk_size if i < 3 else size
        p = Process(target=worker, args=(shared_array, start, end))
        processes.append(p)
        p.start()

    # Wait for all processes to finish
    for p in processes:
        p.join()

    # Convert shared array to numpy array for easy manipulation
    np_array = np.frombuffer(shared_array.get_obj())
    print(f"Sum of squares: {np_array.sum()}")

Dieser Code verkettet drei Coroutinen (fetch_data, process_data und save_result), um eine Pipeline für jede URL zu erstellen. Die Funktion asyncio.gather() führt diese Pipelines dann gleichzeitig aus.

Bei der Arbeit mit lang laufenden Aufgaben ist es oft notwendig, Abbruch- und Timeout-Mechanismen zu implementieren. Hier ist ein Beispiel, das beides demonstriert:

from threading import Lock, Thread

class Counter:
    def __init__(self):
        self.count = 0
        self.lock = Lock()

    def increment(self):
        with self.lock:
            self.count += 1

def worker(counter, num_increments):
    for _ in range(num_increments):
        counter.increment()

counter = Counter()
threads = []
for _ in range(10):
    t = Thread(target=worker, args=(counter, 100000))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"Final count: {counter.count}")

Dieser Code startet fünf lang laufende Aufgaben, legt jedoch ein Zeitlimit von 5 Sekunden für den Abschluss aller Aufgaben fest. Wenn das Timeout erreicht ist, werden alle verbleibenden Aufgaben abgebrochen.

Zusammenfassend lässt sich sagen, dass die Möglichkeiten der gleichzeitigen Programmierung von Python eine breite Palette an Werkzeugen und Techniken zum Schreiben von effizientem, parallelem Code bieten. Von der asynchronen Programmierung mit Asyncio bis hin zum Multiprocessing für CPU-gebundene Aufgaben können diese fortschrittlichen Techniken die Leistung unserer Anwendungen erheblich verbessern. Es ist jedoch von entscheidender Bedeutung, die zugrunde liegenden Konzepte zu verstehen, für jede Aufgabe das richtige Tool auszuwählen und gemeinsam genutzte Ressourcen und potenzielle Rennbedingungen sorgfältig zu verwalten. Mit Übung und sorgfältigem Design können wir die volle Leistungsfähigkeit der gleichzeitigen Programmierung in Python nutzen, um schnelle, skalierbare und reaktionsfähige Anwendungen zu erstellen.


Unsere Kreationen

Schauen Sie sich unbedingt unsere Kreationen an:

Investor Central | Intelligentes Leben | Epochen & Echos | Rätselhafte Geheimnisse | Hindutva | Elite-Entwickler | JS-Schulen


Wir sind auf Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Wissenschaft & Epochen Medium | Modernes Hindutva

Das obige ist der detaillierte Inhalt vonBeherrschen Sie die gleichzeitige Programmierung von Python: Steigern Sie die Leistung mit erweiterten Techniken. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn