Esistono diversi modi per supportare l'operazione multiprocesso per un'applicazione Twisted. Una domanda importante a cui rispondere all'inizio, tuttavia, è ciò che si aspetta che il modello di concorrenza sia, e in che modo la tua applicazione si occupa dello stato condiviso.
In un'applicazione a processo singolo Twisted, la concorrenza è del tutto cooperativa (con l'aiuto delle API I/O asincrone di Twisted) e lo stato condiviso può essere conservato ovunque si trovi un oggetto Python. Il tuo codice di applicazione funziona sapendo che, fino a quando non ha il controllo, nient'altro funzionerà. Inoltre, qualsiasi parte della tua applicazione che vuole accedere ad un pezzo di stato condiviso può probabilmente farlo abbastanza facilmente, dal momento che quello stato è probabilmente conservato in un noioso vecchio oggetto Python che è di facile accesso.
Quando si dispone di più processi, anche se tutti eseguono applicazioni basate su Twisted, esistono due forme di concorrenza. Uno è lo stesso del caso precedente: all'interno di un particolare processo, la concorrenza è cooperativa. Tuttavia, hai un nuovo tipo, in cui sono in esecuzione più processi. Lo scheduler dei processi della tua piattaforma potrebbe cambiare l'esecuzione tra questi processi in qualsiasi momento, e tu hai pochissimo controllo su questo (e anche poca visibilità su quando accade). Potrebbe persino programmare l'esecuzione simultanea di due processi su diversi core (probabilmente è anche quello che speri). Ciò significa che si perdono alcune garanzie sulla coerenza, poiché un processo non sa quando un secondo processo potrebbe arrivare e provare a operare su uno stato condiviso. Questo porta all'altra area importante di considerazione, in cui condividerai effettivamente lo stato tra i processi.
A differenza del singolo modello di processo, non si dispone più di luoghi convenienti e facilmente accessibili per memorizzare lo stato in cui tutto il codice può raggiungerlo. Se lo inserisci in un unico processo, tutto il codice in quel processo può accedervi facilmente come un normale oggetto Python, ma qualsiasi codice in esecuzione in uno qualsiasi degli altri tuoi processi non ha più un facile accesso ad esso. Potrebbe essere necessario trovare un sistema RPC per consentire ai processi di comunicare tra loro.In alternativa, è possibile progettare la divisione del processo in modo che ogni processo riceva solo richieste che richiedono lo stato memorizzato in tale processo. Un esempio di questo potrebbe essere un sito Web con sessioni, in cui tutti gli stati relativi a un utente sono memorizzati nella loro sessione e le loro sessioni sono identificate dai cookie. Un processo front-end può ricevere richieste web, ispezionare il cookie, cercare quale processo back-end è responsabile per quella sessione e quindi inoltrare la richiesta a tale processo back-end. Questo schema significa che il back-end in genere non ha bisogno di comunicare (purché la tua applicazione web sia sufficientemente semplice - cioè, purché gli utenti non interagiscano tra loro, o operino su dati condivisi).
Si noti che in questo esempio, un modello pre-forking non è appropriato. Il processo front-end deve possedere esclusivamente la porta di ascolto in modo che possa ispezionare tutte le richieste in arrivo prima che vengano gestite da un processo di back-end.
Naturalmente ci sono molti tipi di applicazioni, con molti altri modelli per la gestione dello stato. La selezione del modello giusto per la multielaborazione richiede innanzitutto di capire quale tipo di concorrenza ha senso per l'applicazione e come è possibile gestire lo stato dell'applicazione.
Detto questo, con molto di nuove versioni e ritorto (inediti a partire da questo punto), è abbastanza facile per condividere una porta TCP di ascolto tra più processi. Ecco un frammento di codice che dimostra in un modo si potrebbe utilizzare alcune nuove API per raggiungere questo obiettivo:
from os import environ
from sys import argv, executable
from socket import AF_INET
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main(fd=None):
root = File("/var/www")
factory = Site(root)
if fd is None:
# Create a new listening port and several other processes to help out.
port = reactor.listenTCP(8080, factory)
for i in range(3):
reactor.spawnProcess(
None, executable, [executable, __file__, str(port.fileno())],
childFDs={0: 0, 1: 1, 2: 2, port.fileno(): port.fileno()},
env=environ)
else:
# Another process created the port, just start listening on it.
port = reactor.adoptStreamPort(fd, AF_INET, factory)
reactor.run()
if __name__ == '__main__':
if len(argv) == 1:
main()
else:
main(int(argv[1]))
Con le versioni più vecchie, a volte si può ottenere via con l'utilizzo di fork
per condividere la porta. Tuttavia, questo è piuttosto soggetto ad errori, non riesce in alcune piattaforme, e non è un metodo supportato per utilizzare twisted:
from os import fork
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main():
root = File("/var/www")
factory = Site(root)
# Create a new listening port
port = reactor.listenTCP(8080, factory)
# Create a few more processes to also service that port
for i in range(3):
if fork() == 0:
# Proceed immediately onward in the children.
# The parent will continue the for loop.
break
reactor.run()
if __name__ == '__main__':
main()
Questo funziona a causa del normale comportamento di forcella, dove il processo appena creato (il bambino) eredita tutta la memoria e i descrittori di file dal processo originale (il genitore). Poiché i processi sono altrimenti isolati, i due processi non interferiscono tra loro, almeno per quanto riguarda il codice Python che stanno eseguendo. Poiché i descrittori di file sono ereditati, il genitore o uno dei bambini possono accettare connessioni sulla porta.
Da inoltrare richieste HTTP è un compito così facile, dubito si noterà molto di un miglioramento delle prestazioni utilizzando una di queste tecniche. Il primo è un po 'più bello del proxy, perché semplifica la distribuzione e funziona più facilmente per applicazioni non HTTP. Quest'ultimo è probabilmente più di una responsabilità che vale la pena accettare.
Jean-Paul, prima di tutto, grazie per questa risposta dettagliata. Molto apprezzato! La mia applicazione è piuttosto semplice, non esiste uno stato condiviso tra i processi, nessuna sessione da gestire, nessuna necessità di comunicazione tra i processi, ecc. Il mio compito è adatto per il calcolo parallelo e (in teoria) davvero non lo faccio t attenzione se i miei processi girano tutti sullo stesso server, o ogni processo viene eseguito sul proprio computer. Il motivo per cui ho bisogno di utilizzare server più grandi con CPU multi-core è perché Amazon offre prestazioni I/O migliori su quelle. –
ha sfogliato un po 'l'origine del tronco Twisted (posixbase.py, tcp.py), e sembra che le modifiche per riutilizzare il socket preesistente siano davvero nuove. Cercherò di tenere d'occhio queste nuove funzionalità e sperando di vederle nella versione 12.1 :) –
Ho fatto alcuni test e ci sono 2 domande che mi inculcano: 1) non solo i lavoratori, ma anche il master accetterà le connessioni in entrata - come posso far accettare solo i lavoratori? 2) è corretto che l'impostazione di 'backlog' sulla chiamata' reactor.listenTCP' si applicherà al socket in totale - questa è la profondità della coda nel kernel, e quindi per tutti i lavoratori combinati? – oberstet