Python Probleme mit gemeinsam genutztem Speicher beheben und gemeinsam genutzte Ressourcen sperren

Salman Mehmood 21 Juni 2023
  1. Verwenden Sie multiprocessing.Array(), um Shared Memory in Python zu verwenden
  2. Verwenden Sie multiprocessing.Lock(), um die freigegebenen Ressourcen in Python zu sperren
Python Probleme mit gemeinsam genutztem Speicher beheben und gemeinsam genutzte Ressourcen sperren

Dieses Tutorial erläutert verschiedene Aspekte von Multiprocessing Shared Memory und zeigt, wie Probleme mit Shared Memory behoben werden können. Wir werden auch lernen, wie man die Sperre verwendet, um die gemeinsam genutzten Ressourcen in Python zu sperren.

Verwenden Sie multiprocessing.Array(), um Shared Memory in Python zu verwenden

Einer der kritischsten Aspekte von multiprocessing ist die gemeinsame Nutzung der Daten zwischen den Prozessen, wenn Sie mehrere untergeordnete Prozesse haben.

Eine der wesentlichen Eigenschaften der Kindprozesse, die Sie mit Hilfe der Fähigkeiten des processing-Moduls erstellen, ist, dass sie unabhängig laufen und über einen eigenen Speicherplatz verfügen.

Das bedeutet, dass der Prozess des Kindes etwas Speicherplatz haben wird. Und jede Variable versucht, in ihrem eigenen Speicherbereich zu erstellen oder wird geändert, nicht im Speicherbereich ihres übergeordneten Elements.

Lassen Sie uns versuchen, dieses Konzept zu verstehen, indem wir ein Beispiel nehmen und in den Code springen, indem wir das multiprocessing-Modul importieren.

Wir haben eine leere Liste namens RESULT erstellt und eine Funktion namens Make_Sqaured_List() definiert, die die Elemente einer gegebenen Liste quadriert und an unsere globale RESULT-Liste anhängt.

Das Objekt Procc_1 entspricht der Klasse Process() und setzt als Ziel die Funktion Make_Sqaured_List ohne Klammern.

Und an den Parameter args übergeben wir eine separate Liste namens Items_list, die als Argument an die Funktion Make_Sqaured_List() übergeben wird.

Beispielcode:

import multiprocessing

RESULT = []


def Make_Sqaured_List(Num_List):
    global RESULT

    for n in Num_List:
        RESULT.append(n ** 2)
    print(f"Result: {RESULT}")


if __name__ == "__main__":
    Items_list = [5, 6, 7, 8]

    Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
    Procc_1.start()
    Procc_1.join()

Lassen Sie uns diesen untergeordneten Prozess ausführen, und wir erhalten das Ergebnis gemäß unserem untergeordneten Prozess, nämlich den Wert der globalen Liste.

Result: [25, 36, 49, 64]

Aber wenn Sie versuchen, die noch leere Liste ERGEBNIS zu drucken, was passiert dann mit der Liste ERGEBNIS?

print(RESULT)

Ausgang:

[]

Gemäss unserem Hauptprozess ist unser Elternprozess noch leer, während gemäss dem Kindprozess die RESULT-Liste Inhalt hat. Es bedeutet einfach, dass unsere beiden unterschiedlichen Prozesse unterschiedliche Speicherbereiche haben.

Wir können dies anhand eines Szenarios verstehen, in dem wir Prozess 1 haben, das ist unser Hauptprogramm, in dem wir zunächst eine leere RESULT-Liste haben.

Und wenn wir einen untergeordneten Prozess erstellen, hat dieser zunächst ebenfalls einen leeren Wert, und dann wird die Funktion Make_Sqaured_List() ausgeführt, sodass die Liste RESULT einige Elemente enthält. Da wir aber in diesem Speicherbereich vom übergeordneten Prozess auf RESULT zugreifen, sind die Änderungen nicht sichtbar.

Beispielcode:

import multiprocessing

RESULT = []


def Make_Sqaured_List(Num_List):
    global RESULT

    for n in Num_List:
        RESULT.append(n ** 2)
    print(f"Result: {RESULT}")


if __name__ == "__main__":
    Items_list = [5, 6, 7, 8]

    Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
    Procc_1.start()
    Procc_1.join()
    print(RESULT)

Ausgang:

Result: [25, 36, 49, 64]
[]

Also, was ist die Lösung dafür? Aber zuerst wollen wir sehen, wie wir es lösen können.

Lösung zur Behebung der Probleme bei der gemeinsamen Nutzung von Daten zwischen Multiprocessing

In diesem Abschnitt werden wir die Lösung sehen, die uns hilft, den Wert aller Änderungen zu ermitteln und das Problem mit der gemeinsamen Nutzung von Daten zwischen den Multiprocessing zu beheben.

Die Lösung heißt Shared Memory. Das multiprocessing-Modul stellt uns zwei Arten von Objekten namens Array und Value zur Verfügung, die verwendet werden können, um die Daten zwischen den Prozessen zu teilen.

Das Array ist ein Array, das aus dem gemeinsam genutzten Speicher zugewiesen wird; Im Grunde gibt es einen Teil Ihres Computerspeichers, den wir gemeinsam genutzten Speicher nennen können, oder einen Bereich, auf den mehrere Prozesse zugreifen können.

In diesem gemeinsamen Speicher erstellen wir also ein neues Array oder einen neuen Wert. Diese Mehrwerte sind nicht unsere grundlegenden Python-Datenstrukturen; Es gibt etwas anderes und ist im Modul multiprocessing selbst definiert.

Nun deklarieren wir ein Objekt namens RESULT_ARRAY mit multiprocessing.Array(). Dann müssen wir in diesem Array den Datentyp übergeben. Wir übergeben i als String, was bedeutet, dass wir ganzzahlige Werte darin einfügen, und wir müssen die Größe angeben.

Beispielcode:

RESULT_ARRAY = multiprocessing.Array("i", 4)

Es ist mit einem Array im C-Programmierstil verwandt, sodass wir die Größe gleichzeitig angeben können. Auf diese Weise können wir Objekte am gewünschten Ort speichern.

Jetzt erstellen wir einen neuen Wert namens OBJ_Sum, der gleich multiprocessing.Value() ist, und der Wert wird gespeichert und eingegeben.

Beispielcode:

OBJ_Sum = multiprocessing.Value("i")

Als nächstes erstellen wir ein Objekt namens procc_1, das gleich multiprocessing.Process() ist, das wir eine Funktion nennen werden. Wir haben eine Funktion namens Make_Sqaured_List() erstellt, die drei Argumente annehmen wird:

  1. Eine Liste
  2. Ein Array-Objekt
  3. Ein Wertobjekt.

Wir werden diese drei Argumente mit diesem Process-Argument namens args an unsere Funktion übergeben. Sehen Sie sich beispielsweise den folgenden Codezaun an.

Beispielcode:

Procc_1 = multiprocessing.Process(
    target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum)
)

In der Funktion Make_Sqaured_List() werden wir nun Items_list mit der Funktion enumerate() iterieren. Damit wir den index und Wert von Items_list erhalten können.

Es ist ein Array im C-Stil, daher müssen wir die Indizierung verwenden, um die Werte unserem Array zuzuweisen. Wir werden auch die Werte eines Arrays summieren, und OBJ_Sum.value ist eine Eigenschaft von multiprocessing.Value().

Beispielcode:

def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):

    for i, n in enumerate(Items_list):
        RESULT[i] = n ** 2

    OBJ_Sum.value = sum(RESULT)

Wir haben in unserem Hauptprozess einige Variablen definiert, die die vom untergeordneten Prozess aufgerufene Funktion ändern. Unsere Hauptagenda ist also, ob wir diese geänderten Werte in unseren Hauptprozess übernehmen können oder nicht.

Jetzt können wir auf das im untergeordneten Prozess reflektierte Array zugreifen und seine Summe mit OBJ_Sum.value erhalten. Sehen Sie sich beispielsweise das folgende Code-Snippet an.

Beispielcode:

import multiprocessing


def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):

    for i, n in enumerate(Items_list):
        RESULT[i] = n ** 2

    OBJ_Sum.value = sum(RESULT)


if __name__ == "__main__":
    Items_list = [5, 6, 7, 8]

    RESULT_ARRAY = multiprocessing.Array("i", 4)
    OBJ_Sum = multiprocessing.Value("i")

    Procc_1 = multiprocessing.Process(
        target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum)
    )
    Procc_1.start()
    Procc_1.join()
    print(RESULT_ARRAY[:])
    print(OBJ_Sum.value)

Ausgang:

[25, 36, 49, 64]
174

Auf diese Weise können wir alle Änderungen an unseren Objekten vornehmen, die in unserem übergeordneten Prozess definiert sind, und die Änderungen, die wir vom untergeordneten Prozess zurückerhalten. Möglich wird dies durch die Shared-Memory-Technik.

Verwenden Sie multiprocessing.Lock(), um die freigegebenen Ressourcen in Python zu sperren

Wir werden ein entscheidendes Thema namens Schloss behandeln; Nun, wenn Sie Informatik- oder Betriebssystemkurse besucht haben, haben Sie bereits etwas über das Schloss gelernt. Allerdings ist die Sperre ein kritischer Begriff, wenn es um multiprocessing und Betriebssystemkonzepte geht.

Lassen Sie uns zuerst überlegen, warum das Schloss im wirklichen Leben benötigt wird; In unserem täglichen Leben können einige Ressourcen nicht von zwei Personen gleichzeitig aufgerufen werden.

Zum Beispiel hat die Badezimmertür ein Schloss, denn wenn zwei Personen gleichzeitig versuchen, darauf zuzugreifen, entsteht eine ziemlich peinliche Situation. Deshalb schützen wir das Badezimmer, eine gemeinsame Ressource mit einem Schloss.

Ähnlich verhält es sich in der Programmierwelt, wenn zwei Prozesse oder Threads versuchen, auf eine gemeinsam genutzte Ressource zuzugreifen, z. B. eine gemeinsam genutzte Speicherdatei oder eine Datenbank. Dies kann zu Problemen führen, daher müssen Sie diesen Zugriff mit einer Sperre schützen.

Was passiert, wenn wir diesen Schutz nicht zu unserem Programm hinzufügen, werden wir anhand eines Beispiels sehen. Auch dies ist ein Banking-Softwareprogramm, und hier haben wir zwei Prozesse.

Der erste Prozess ist die Einzahlung von Geld in eine Bank mit der Funktion MONEY_DP() und der zweite das Abheben von Geld von der Bank mit der Funktion MONEY_WD(). Und am Ende drucken wir die Endbilanz.

Wir beginnen mit einem Guthaben von 200$ im Abschnitt MONEY_DP. Wir zahlen 100$ ein, wir haben eine for-Schleife, die wir 100-mal durchlaufen haben, und bei jeder Iteration werden unserem Bankkonto 01$ hinzugefügt.

In ähnlicher Weise haben wir in der Funktion MONEY_WD dieselbe Schleife, die 100 Mal wiederholt wird, und jedes Mal wird 1 Dollar von unserem Bankkonto abgezogen.

Beispielcode:

import multiprocessing
import time


def MONEY_DP(B):
    for i in range(100):
        time.sleep(0.01)
        B.value = B.value + 1


def MONEY_WD(B):
    for i in range(100):
        time.sleep(0.01)
        B.value = B.value - 1

Jetzt verwenden wir eine Shared-Memory-Variable namens value, die wir bereits im vorherigen Abschnitt kennengelernt haben. Dieser multiprocessing-Wert ist eine gemeinsam genutzte Speicherressource. Sehen wir uns also an, was passiert, wenn wir versuchen, dieses Programm auszuführen.

if __name__ == "__main__":
    B = multiprocessing.Value("i", 200)
    Deposit = multiprocessing.Process(target=MONEY_DP, args=(B,))
    Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B,))
    Deposit.start()
    Withdrawl.start()
    Deposit.join()
    Withdrawl.join()
    print(B.value)

Wir werden es mehrmals ausführen, und jedes Mal werden nur andere Werte gedruckt, aber es sollte 200$ drucken.

Ausgang:

# execution 1
205
# execution 2
201
# execution 3
193

Warum passiert das? Dies geschieht hauptsächlich, wenn dieser Prozess versucht, eine Variable namens B.value im gemeinsamen Speicher zu lesen.

Sagen wir, B.value hat einen 200$-Wert, es liest ihn, fügt dann eins hinzu und fügt dasselbe wieder in dieselbe Variable ein.

Da B.value 200$ ist und diese Additionsoperation auf Betriebssystemebene durchführt, werden auf Betriebssystemebene mehrere Fließbandanweisungen ausgeführt.

Wir haben also die Variable 200 gelesen; es wird eins hinzugefügt und weist der Variablen B.value 201 zurück.

Beispielcode:

B.value = B.value + 1

Jetzt, während es dies gleichzeitig tut, wurde diese Anweisung auch in der Funktion MONEY_WD() ausgeführt.

Beispielcode:

B.value = B.value - 1

Obwohl wir zuerst einzahlen und dann abheben, wird der Prozess, wenn er versucht, B.value zu lesen, immer noch 200 sein, weil der Einzahlungsprozess nicht auf die ursprüngliche Variable zurückgeschrieben hat.

Anstatt B.value als 201 innerhalb des MONEY_WD-Prozesses zu lesen, wird B.value als 200 gelesen, und nach dem Verringern von 1 wird es 199 haben.

Deshalb bekommen wir inkonsistentes Verhalten. Lassen Sie uns zuerst Lock verwenden, um den Zugang zu sperren; Jetzt erstellen wir eine Variable namens Lock und verwenden multiprocessing-Module, um die Lock-Klasse zu verwenden.

Beispielcode:

lock = multiprocessing.Lock()

Jetzt übergeben wir diese Sperre an beide Prozesse und innerhalb beider Prozesse rufen wir lock.acquire() auf, um eine Sperre zu setzen, und um dann eine Sperre freizugeben, rufen wir lock.release() Funktion.

Diese lock-Funktionen schützen den Codeabschnitt beim Zugriff auf die gemeinsam genutzte Ressource, den so genannten kritischen Abschnitt.

Beispielcode:

import multiprocessing
import time


def MONEY_DP(B, lock):
    for i in range(100):
        time.sleep(0.01)
        lock.acquire()
        B.value = B.value + 1
        lock.release()


def MONEY_WD(B, lock):
    for i in range(100):
        time.sleep(0.01)
        lock.acquire()
        B.value = B.value - 1
        lock.release()


if __name__ == "__main__":
    B = multiprocessing.Value("i", 200)
    lock = multiprocessing.Lock()
    Deposit = multiprocessing.Process(target=MONEY_DP, args=(B, lock))
    Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B, lock))
    Deposit.start()
    Withdrawl.start()
    Deposit.join()
    Withdrawl.join()
    print(B.value)

Jetzt druckt dieser Code jedes Mal 200.

Ausgang:

200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Verwandter Artikel - Python Error