Ich hoffe, es ist in Ordnung, wenn ich meine Notizen zu diesem Thema hier niederschreibe.
Zunächst einmal schätze ich das Beispiel im OP sehr, denn damit habe ich auch angefangen - obwohl es mich zum Nachdenken gebracht hat shared
ist ein eingebautes Python-Modul, bis ich ein vollständiges Beispiel unter [Tutor] Globale Variablen zwischen Modulen ?? .
Als ich jedoch nach der "gemeinsamen Nutzung von Variablen zwischen Skripten" (oder Prozessen) suchte - abgesehen von dem Fall, dass ein Python-Skript Variablen verwenden muss, die in anderen Python-Quelldateien (aber nicht unbedingt in laufenden Prozessen) definiert sind -, stieß ich hauptsächlich auf zwei andere Anwendungsfälle:
- Ein Skript teilt sich selbst in mehrere untergeordnete Prozesse auf, die dann parallel (möglicherweise auf mehreren Prozessoren) auf demselben PC laufen
- Ein Skript legt mehrere andere untergeordnete Prozesse an, die dann parallel (möglicherweise auf mehreren Prozessoren) auf demselben PC laufen
Die meisten Treffer in Bezug auf "gemeinsam genutzte Variablen" und "Interprozesskommunikation" (IPC) behandeln Fälle wie diese beiden; in beiden Fällen ist jedoch ein "Elternteil" zu beobachten, auf den die "Kinder" in der Regel einen Verweis haben.
Was mich jedoch interessiert, ist die Ausführung mehrerer Aufrufe desselben Skripts, die unabhängig voneinander ausgeführt werden, und die gemeinsame Nutzung von Daten zwischen diesen (wie in Python: Gemeinsame Nutzung einer Objektinstanz bei mehreren Aufrufen eines Skripts ), in einem Singleton/Single-Instance-Modus. Diese Art von Problem wird durch die beiden oben genannten Fälle nicht wirklich gelöst - stattdessen reduziert es sich im Wesentlichen auf das Beispiel in OP (gemeinsame Nutzung von Variablen in zwei Skripten).
Wenn man dieses Problem in Perl behandelt, gibt es IPC::Shareable die es "ermöglicht, eine Variable an einen gemeinsamen Speicher zu binden", indem "eine Ganzzahl oder eine 4-stellige Zeichenkette[1] verwendet wird, die als gemeinsamer Bezeichner für Daten im gesamten Prozessraum dient". Es gibt also keine temporären Dateien und keine Netzwerkeinstellungen - was ich für meinen Anwendungsfall großartig finde; deshalb habe ich nach dem gleichen in Python gesucht.
Da jedoch akzeptierte Antwort von @Drewfer Anmerkungen: " Sie werden nicht in der Lage sein, das zu tun, was Sie wollen, ohne die Informationen irgendwo außerhalb der beiden Instanzen des Interpreters zu speichern "; oder mit anderen Worten: entweder müssen Sie ein Netzwerk/Socket-Setup verwenden - oder Sie müssen temporäre Dateien verwenden (ergo, kein gemeinsames RAM für " völlig getrennte Python-Sitzungen ").
Trotz dieser Überlegungen ist es ziemlich schwierig, funktionierende Beispiele zu finden (außer für pickle
) - auch in den Dokumenten für mmap y Multiprozessorbetrieb . Es ist mir gelungen, einige andere Beispiele zu finden - die auch einige Fallstricke beschreiben, die in den Unterlagen nicht erwähnt werden:
- Verwendung von
mmap
: funktionierender Code in zwei verschiedenen Skripten unter Gemeinsame Nutzung von Python-Daten zwischen Prozessen mit mmap | schmichael's blog
- Zeigt, wie beide Skripte den gemeinsamen Wert verändern
- Beachten Sie, dass hier eine temporäre Datei als Speicher für gespeicherte Daten angelegt wird -
mmap
ist nur eine spezielle Schnittstelle für den Zugriff auf diese temporäre Datei
- Verwendung von
multiprocessing
: funktionierender Code unter:
Dank dieser Beispiele habe ich mir ein Beispiel ausgedacht, das im Wesentlichen dasselbe tut wie die mmap
Beispiel, mit Ansätzen aus dem " ein Python-Diktat synchronisieren " Beispiel - mit BaseManager
(über manager.start()
durch Dateipfadadresse) mit gemeinsamer Liste; sowohl Server als auch Client lesen und schreiben (unten eingefügt). Beachten Sie das:
multiprocessing
Manager können entweder über manager.start()
o server.serve_forever()
serve_forever()
Schlösser - start()
nicht
- Es gibt eine automatische Protokollierungsfunktion in
multiprocessing
: Es scheint gut zu funktionieren mit start()
Prozesse - scheint aber die Prozesse zu ignorieren, die serve_forever()
- Die Adressangabe in
multiprocessing
kann ein IP-Pfad (Socket) oder eine temporäre Datei (möglicherweise eine Pipe?) sein; in multiprocessing
docs:
- Die meisten Beispiele verwenden
multiprocessing.Manager()
- ist dies nur eine Funktion ( nicht Instanziierung der Klasse), die eine SyncManager
, die eine spezielle Unterklasse von BaseManager
; und Verwendungen start()
- sondern nicht für IPC zwischen unabhängig laufenden Skripten; hier wird ein Dateipfad verwendet
- Einige weitere Beispiele
serve_forever()
Ansatz für IPC zwischen unabhängig laufenden Skripten; hier wird die IP/Socket-Adresse verwendet
- Wenn keine Adresse angegeben ist, wird automatisch ein temporärer Dateipfad verwendet (siehe 16.6.2.12. Protokollierung für ein Beispiel, wie man dies sehen kann)
Zusätzlich zu all den Fallstricken in der " ein Python-Diktat synchronisieren "Bei einer Liste gibt es zusätzliche Einträge. Dieser Beitrag stellt fest:
Alle Manipulationen des Diktats müssen mit Methoden und nicht mit Diktatzuweisungen erfolgen (syncdict["blast"] = 2 wird aufgrund der Art und Weise, wie die Mehrprozessoren benutzerdefinierte Objekte gemeinsam nutzen, kläglich scheitern)
Die Umgehungslösung für dict['key']
und Einstellung, ist die Verwendung des dict
öffentliche Methoden get
y update
. Das Problem ist, dass es keine solchen öffentlichen Methoden als Alternative für list[index]
für eine gemeinsame Liste müssen wir also zusätzlich registrieren __getitem__
y __setitem__
Methoden (die privat sind für list
) als exposed
, was bedeutet, dass wir auch alle öffentlichen Methoden für list
außerdem :/
Nun, ich denke, das waren die kritischsten Dinge; dies sind die beiden Skripte - sie können einfach in separaten Terminals ausgeführt werden (Server zuerst); Anmerkung: entwickelt auf Linux mit Python 2.7:
a.py
(Server):
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
syncarr = []
def get_arr():
return syncarr
def main():
# print dir([]) # cannot do `exposed = dir([])`!! manually:
MyListManager.register("syncarr", get_arr, exposed=['__getitem__', '__setitem__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'])
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.start()
# we don't use the same name as `syncarr` here (although we could);
# just to see that `syncarr_tmp` is actually <AutoProxy[syncarr] object>
# so we also have to expose `__str__` method in order to print its list values!
syncarr_tmp = manager.syncarr()
print("syncarr (master):", syncarr, "syncarr_tmp:", syncarr_tmp)
print("syncarr initial:", syncarr_tmp.__str__())
syncarr_tmp.append(140)
syncarr_tmp.append("hello")
print("syncarr set:", str(syncarr_tmp))
raw_input('Now run b.py and press ENTER')
print
print 'Changing [0]'
syncarr_tmp.__setitem__(0, 250)
print 'Changing [1]'
syncarr_tmp.__setitem__(1, "foo")
new_i = raw_input('Enter a new int value for [0]: ')
syncarr_tmp.__setitem__(0, int(new_i))
raw_input("Press any key (NOT Ctrl-C!) to kill server (but kill client first)".center(50, "-"))
manager.shutdown()
if __name__ == '__main__':
main()
b.py
(Kunde)
import time
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
MyListManager.register("syncarr")
def main():
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.connect()
syncarr = manager.syncarr()
print "arr = %s" % (dir(syncarr))
# note here we need not bother with __str__
# syncarr can be printed as a list without a problem:
print "List at start:", syncarr
print "Changing from client"
syncarr.append(30)
print "List now:", syncarr
o0 = None
o1 = None
while 1:
new_0 = syncarr.__getitem__(0) # syncarr[0]
new_1 = syncarr.__getitem__(1) # syncarr[1]
if o0 != new_0 or o1 != new_1:
print 'o0: %s => %s' % (str(o0), str(new_0))
print 'o1: %s => %s' % (str(o1), str(new_1))
print "List is:", syncarr
print 'Press Ctrl-C to exit'
o0 = new_0
o1 = new_1
time.sleep(1)
if __name__ == '__main__':
main()
Eine letzte Bemerkung zu Linux /tmp/mypipe
wird erstellt - hat aber 0 Bytes und die Attribute srwxr-xr-x
(für einen Socket); ich schätze, das macht mich glücklich, da ich mich weder um Netzwerk-Ports noch um temporäre Dateien als solche kümmern muss :)
Andere damit zusammenhängende Fragen:
7 Stimmen
Meinen Sie, dass Sie einen Wert in zwei völlig getrennten Python-Sitzungen gemeinsam nutzen wollen? Wenn ja, ist das nicht möglich, indem Sie einfach einen Wert in einem gemeinsamen Modul setzen.
0 Stimmen
Ich danke Ihnen. Im Moment bin ich dabei, ein drittes Modul zu erstellen, das beide importiert, so dass alles in der gleichen Sitzung geschieht. Die Pickle-Lösung funktioniert nicht ganz für mich, weil ich eine threading.Condition()-Variable in einem Modul setzen wollte (die wegen der impliziten Sperre nicht gepickt werden kann) und sie von beiden Sitzungen aus verwenden wollte. Und jetzt, wo ich darüber nachdenke, ist die Möglichkeit, die Variable gemeinsam zu nutzen, wahrscheinlich das geringste meiner Probleme, da ich nicht glaube, dass der Zustand in der Bedingungsvariable gemeinsam genutzt werden wird.
0 Stimmen
Ich bin auf diese Frage gekommen, als ich das gleiche Problem hatte. Dann habe ich über Steckdosen gelesen. Warum sollte man sie nicht verwenden?