2009-08-18 9 views
16

Sto lavorando su un codice Python modellato sul server prefork MPM di Apache. Sono più un programmatore di applicazioni che un programmatore di rete e sono passati 10 anni da quando ho letto Stevens, quindi sto cercando di capire quanto è veloce il codice.accept() con socket condivisi tra più processi (basato sulla preforking di Apache)

Ho trovato una breve descrizione di how Apache's prefork code works, by Sander Temme.

Il processo padre, che in genere viene eseguito come root, si collega a un socket (in genere porta 80 o 443). Genera figli, che ereditano il descrittore di file aperto per il socket e cambiano uid e gid per l'utente e il gruppo senza privilegi . I bambini costruiscono un sondaggio dei descrittori di file listener (se c'è più di un ascoltatore) e verificano l'attività su di essi. Se viene rilevata un'attività, il bambino chiama accept() sul socket attivo e gestisce la connessione. Quando è fatto con quello, torna a guardare il pollset (o il file listener descrittore).

Poiché più bambini sono attivi e hanno tutti ereditato lo stesso file descrittore di socket , essi guarderanno lo stesso pollset. Un mutex accettato consente a un solo bambino di guardare effettivamente il sondaggio, e una volta che ha trovato un socket attivo, sbloccherà il mutex in modo che il bambino successivo possa iniziare a guardare il sondaggio. Se è presente un solo listener , quello accetta il mutex non viene utilizzato e tutti i child si bloccheranno in accept().

Questo è il modo in cui funziona il codice che sto guardando, ma non capisco alcune cose.

1) Qual è la differenza tra un "bambino" e un "ascoltatore"? Ho pensato che ogni bambino fosse un ascoltatore, il che è vero per il codice che sto guardando, ma nella descrizione di Temme possono esserci "un singolo ascoltatore" e "bambini". Quando un bambino avrebbe più ascoltatori?

2) (correlato a 1) È un mutex per processo o un mutex di sistema? Del resto, perché avere un mutex? Non accetta (2) il proprio mutex su tutti gli ascoltatori? La mia ricerca dice che ho bisogno di un mutex e che il mutex deve attraversare l'intero sistema. (Gregge, semafori, ecc)

Temme continua a dire:

bambini registrare in un'area di memoria condivisa (il quadro di valutazione) quando durano servito una richiesta. I bambini inattivi possono essere essere ucciso dal processo padre a soddisfare MaxSpareServers. Se un numero troppo basso di bambini è inattivo, il genitore eseguirà la generazione dei figli per soddisfare MinSpareServers.

3) Esiste un buon codice di riferimento per questa implementazione (preferibilmente in Python)? Ho trovato Perl's Net::Server::Prefork, che utilizza pipe invece di memoria condivisa per il tabellone. Ho trovato un articolo di Randal Schwartz che fa solo il preforking ma non fa il quadro di valutazione.

Il pre-fork example from the Perl Cookbook non ha alcun tipo di blocco intorno a selezionare, e Chris Siebenmann's Python example dice che è basato su Apache, ma utilizza i socket appaiati per il quadro di valutazione, la memoria non condivisa, e utilizzare le prese per i controlli, includere il controllo per un dato bambino 'accettare. Questo non corrisponde affatto alla descrizione di Apache.

+0

Stai usando qualcosa come 'mod_wsgi' come interfaccia tra Apache e Python? Se è così, dovrebbe gestire tutto questo per te. –

+0

Questo è per un server WSGI di prefigurazione Python puro. Il mio cliente desidera una soluzione leggera per i luoghi che non desiderano Apache e mod_wsgi o equivalenti. L'unico server WSGI che ho trovato in Python era Spawning e richiede l'eventlet. ... Anche se ora ho scoperto che il flup ha un'implementazione come quella di Siebenmann che usa pipe per il tabellone segnaposto invece di memoria condivisa e con una licenza accettabile per il mio cliente. –

risposta

15

Rispetto a (1), l'ascoltatore è semplicemente un riferimento all'esistenza di socket su cui accettare le connessioni. Poiché Apache può accettare connessioni su più socket contemporaneamente, ad es. 80/443, ci sono più socket listener. Ogni processo figlio dovrebbe ascoltare tutte queste prese quando arriva il momento. Poiché accept() può essere eseguito solo su un socket alla volta, è preceduto dal polling/select in modo che sia noto su quale socket del listener debba essere eseguito l'accept.

Rispetto a (2), è un mutex globale o incrociato. Cioè, un processo che blocca bloccherà altri processi che cercano di acquisire lo stesso blocco. Sebbene accept() serializzi tecnicamente i processi, la presenza di più socket listener significa che non si può fare affidamento su ciò in quanto non si conosce a priori su quale socket eseguire. Anche nel caso di un singolo socket listener, la ragione per il mutex accept è che se ci sono un gran numero di processi che gestiscono le richieste, allora potrebbe essere piuttosto costoso se il sistema operativo riattiva tutti i processi per vedere quale accetta() restituisce per esso. Poiché Apache in modalità prefork può avere più di 100 processi, ciò potrebbe causare un problema.

Quindi, se si dispone di un solo socket listener e si è certi di avere solo pochi processi che desiderano effettuare la chiamata accept(), è possibile eliminare il mutex di processo incrociato.

+0

Grazie Graham! Non pensavo completamente a come possono esserci più ascoltatori, e come ciò influenzerebbe tutto. La mia comprensione ora è molto più chiara, sia sul perché il codice che stavo usando funzionava, sia sul perché sembrava che non funzionasse. –

Problemi correlati