Es ist eigentlich nicht notwendig, die Warteschlangen in diesem Fall in das Argument args
aufzunehmen, egal welche Plattform Sie verwenden. Der Grund ist, dass, obwohl es nicht so aussieht, dass Sie die beiden JoinableQueue
Instanzen explizit an das Kind weitergeben, Sie tatsächlich - über self
. Da self
explizit an das Kind übergeben wird und die beiden Warteschlangen ein Teil von self
sind, werden sie am Kind weitergegeben.
Unter Linux geschieht dies über os.fork()
, was bedeutet, dass Filedeskriptoren von den multiprocessing.connection.Connection
Objekten verwendet, die die Queue
intern verwendet für die Kommunikation zwischen Prozessen sind durch das Kind vererbt (nicht kopiert). Andere Teile der Queue
werden copy-on-write
, aber das ist in Ordnung; multiprocessing.Queue
ist so konzipiert, dass keines der Teile, die kopiert werden müssen, tatsächlich zwischen den beiden Prozessen synchron bleiben muss. In der Tat, erhalten viele der internen Attribute zurückgesetzt, nachdem der fork
auftritt:
def _after_fork(self):
debug('Queue._after_fork()')
self._notempty = threading.Condition(threading.Lock())
self._buffer = collections.deque()
self._thread = None
self._jointhread = None
self._joincancelled = False
self._closed = False
self._close = None
self._send = self._writer.send # _writer is a
self._recv = self._reader.recv
self._poll = self._reader.poll
Damit deckt Linux. Wie wäre es mit Windows? Windows hat fork
nicht, also muss es self
picken, um es dem Kind zu schicken, und das schließt das Beizen unserer Queues
mit ein. Nun, in der Regel, wenn Sie versuchen, ein multiprocessing.Queue
beizen, es scheitert:
>>> import multiprocessing
>>> q = multiprocessing.Queue()
>>> import pickle
>>> pickle.dumps(q)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/pickle.py", line 1374, in dumps
Pickler(file, protocol).dump(obj)
File "/usr/local/lib/python2.7/pickle.py", line 224, in dump
self.save(obj)
File "/usr/local/lib/python2.7/pickle.py", line 306, in save
rv = reduce(self.proto)
File "/usr/local/lib/python2.7/copy_reg.py", line 84, in _reduce_ex
dict = getstate()
File "/usr/local/lib/python2.7/multiprocessing/queues.py", line 77, in __getstate__
assert_spawning(self)
File "/usr/local/lib/python2.7/multiprocessing/forking.py", line 52, in assert_spawning
' through inheritance' % type(self).__name__
RuntimeError: Queue objects should only be shared between processes through inheritance
Aber das ist eigentlich eine künstliche Beschränkung. multiprocessing.Queue
Objekte können in einigen Fällen eingelegt werden - wie sonst könnten sie an untergeordnete Prozesse in Windows gesendet werden? Und in der Tat können wir sehen, ob wir bei der Umsetzung aussehen:
def __getstate__(self):
assert_spawning(self)
return (self._maxsize, self._reader, self._writer,
self._rlock, self._wlock, self._sem, self._opid)
def __setstate__(self, state):
(self._maxsize, self._reader, self._writer,
self._rlock, self._wlock, self._sem, self._opid) = state
self._after_fork()
__getstate__
, die aufgerufen wird, wenn eine Instanz Beizen, hat einen assert_spawning
Anruf darin, die sicherstellt, wir tatsächlich einen Prozess Laichen beim Versuch, die Gurke *. __setstate__
, die beim Entpacken aufgerufen wird, ist verantwortlich für den Aufruf _after_fork
.
Also wie werden die Connection
Objekte von den Warteschlangen verwendet, wenn wir pürieren müssen?Es stellt sich heraus, dass es ein multiprocessing
Untermodul gibt, das genau das tut - multiprocessing.reduction
. Der Kommentar am Anfang des Moduls heißt es ziemlich deutlich:
#
# Module to allow connection and socket objects to be transferred
# between processes
#
Unter Windows das Modul schließlich den von Windows bereitgestellt DuplicateHandle API verwendet, kann einen doppelten Griff, dass das Kind Prozess Connection
Objekt verwenden zu erstellen. So, während jeder Prozess seinen eigenen Griff bekommt, sind sie genaue Duplikate - jede Aktion, die an einer gemacht wird, spiegelt sich auf der anderen:
Der doppelte Griff bezieht sich auf das gleiche Objekt wie der ursprüngliche Griff. Daher werden alle Änderungen am Objekt über beide Handles wiedergegeben. Wenn Sie beispielsweise ein Datei-Handle duplizieren, ist die aktuelle Position der Datei für beide Handles immer gleich.
* Siehe this answer für weitere Informationen über assert_spawning
Dies ist nicht ganz richtig. Unter Linux wird der untergeordnete Prozess vom übergeordneten Zweig abgeleitet, sodass er tatsächlich einen schreibgeschützten Adressraum vom übergeordneten Knoten erhält. Und tatsächlich können Sie in der Warteschlange in das Kind "einsteigen" und das Ergebnis vom Elternteil "abholen", ohne die "Warteschlange" explizit an das Kind unter Linux * und * Windows zu übergeben. Die einzigen Fälle, in denen es nicht zu funktionieren scheint, sind Python 3.4+ mit Linux, die die Kontexte "spawn" oder "forkserver" verwenden. – dano
Eigentlich nehme ich diesen letzten Satz zurück. Das war auf einen Fehler von mir zurückzuführen. Sie können die Warteschlangenobjekte immer implizit übergeben, unabhängig vom Kontext/der Plattform. – dano
+ dano, gute Antwort. Du bist eindeutig ein Multiprozessor-Guru! – Matt