PDA

View Full Version : [C] Chiudere thread/processi inattivi


Manugal
06-04-2010, 12:58
Ciao a tutti.

Sto scrivendo un mail server multiprocesso e multithreaded. Ho un ciclo dove il processo/thread principale crea un certo numero di processi/thread (letto da un file di configurazione). Ho impostato anche un signal handler (e un Console Control Handler nella versione Windows) che intercetta SIGHUP per poter rileggere il file di configurazione a runtime. Il problema è che se abbasso il numero massimo di processi/thread possibili nel file di configurazione, vorrei chiudere solo i processi/thread inattivi piuttosto che riavviare l'intero server (chiudendo tutti i processi/thread come faccio ora). Il problema è che non saprei come individuare un processo/thread inattivo.

Spero di essere stato chiaro.

Teo@Unix
06-04-2010, 16:27
Forse io non ho capito bene quello che intendi però...

se usi la funzione fork() ad ogni nuova connessione del client il processo figlio generato gestirà il client e ok. Finito questo terminerà suppongo. Se hai impostato correttamente nel padre il relativo segnale così:

signal(SIGCHLD,chld_handler_function());

il processo figlio termina correttamente senza rimanere zombie e occupare un posto nella page table.

Se gestisci con i thread questa cosa una volta che il thread fa quello che deve fare e termina ok sei a posto.

Se ad un certo punto l'utente non vuole più usare 20 thread (20 connessioni contemporanee direi) e ne vuole solo 10, e quelli attivi sono 18 io imposterei allora una riga di codice sistemata prima dell'avvio di un nuovo thread che va a controllare il limite massimo con i thread attivi in quel momento... se tale limite è raggiunto o superato, come potrebbe essere successo in questo caso dove è stato abbassato durante l'esecuzione, allora non ne avvierà di nuovi e il client si vedrà un bel messaggio che dice ad esempio "too many connection..ecc ecc..".
Per quelli in esecuzione bè non è che li possiamo sbattere fuori sti poveretti no? Aspettiamo che terminano, il numero delle connessioni attive scenderà primo o poi sotto il nuovo limite.

Non so cosa intendi per thread inattivi quindi...
a meno che tu non hai impostato il programma per avere dei thread con dei cicli infiniti e gestiti da semafori o altro...

Oppure non crei un thread per ogni connessione? I thread fanno altre cose di sistema? In questo caso non so, a meno che di scrivere il programma in modo tale da poter interrompere dei thread però la vedo difficile da fare durante l'esecuzione....

Non so se ho risposto in modo adeguato. Magari qualcuno ha delle idee migliori.
Io così di prima pensata lo farei così.

WarDuck
06-04-2010, 16:30
Tempo fa ho fatto un programma peer to peer con directory server (in pratica un server che tiene traccia dei peer connessi alla rete), usando la libreria pthread (che spero tu stia usando)... in pratica avevo impostato una variabile "occupato" per ogni thread (i quali gestivano connessioni con i "client"): il thread manager ogni 30 secondi si svegliava e faceva un polling interrogando tutti i thread, quelli inattivi si auto-terminavano (chiamando pthread_exit()).

Per mandare un segnale ad un thread c'è la funzione pthread_kill().

Se non ricordo male c'era un accorgimento da tenere in considerazione relativo alla gestione dei segnali a livello di processo piuttosto che a livello di thread, ma questo per il momento ignoralo (tranne se ti trovi davanti a cose strane, tipo segnali ad un thread e termina tutto il processo :asd: ).

Manugal
06-04-2010, 17:03
Grazie delle risposte.

Effettivamente non mi sono spiegato bene. Premetto che è un progetto per l'università e questa cosa non è neanche richiesta nelle specifiche, ma la vorrei fare perché adesso come adesso, quando vado a rileggere il file di configurazione a runtime in pratica chiudo tutti i thread/processi e riavvio il server (a prescindere se sono attivi o no).

Il ciclo principale del programma è il seguente (versione multithreaded):

for(iThd=0; iThd<cfgParams->numOfThrds && !reReadCfg && !shutDown && cnt<2; ){
// Se status != 0
if (tArgs[iThd].status == 1 || tArgs[iThd].status == 3 || tArgs[iThd].status == 4){
#ifdef _WIN32
tStatus = WaitForSingleObject(tArgs[iThd].srv_hndl,INFINITE);
if (tStatus != WAIT_OBJECT_0){
WinMTShutDown(handles,tArgs);
PrintError("Server process wait error: ");
}
CloseHandle(tArgs[iThd].srv_hndl);
#else
if(pthread_join(tArgs[iThd].srv_hndl,NULL)!=0){
LinuxMTShutDown(POP3ThId,SMTPThId,tArgs);
PrintError("Server process wait error: ");
}
#endif
// Richiesta di rilettura del file di configurazione
if(tArgs[iThd].status == 3)
reReadCfg = TRUE;
// Richiesta di shutdown del server
else if(tArgs[iThd].status == 4)
shutDown = TRUE;
// Altrimenti poni lo status=0 (cioè lo slot è libero)
else
tArgs[iThd].status = 0;
}
// Se lo slot è libero allora è possibile assegnarlo a uno dei due thread SMTP o POP3
if(tArgs[iThd].status == 0){
idx[i++]=iThd;
++cnt;
}
// Altrimenti passo all'indice successivo e rifaccio i controlli
iThd = (iThd+1) % cfgParams->numOfThrds;
// Se sono finiti tutti gli slot mi metto in attesa e riprovo
if (iThd == 0){
#ifdef _WIN32
Sleep(3000);
#else
sleep(3);
#endif
}
}


tArgs è una struttura dati che passo ad ogni thread che creo e ne viene passata una diversa per ogni thread. Ignorate quella variabile cnt che mi serve per poter fare il ciclo almeno due volte (per assegnare una tArgs diversa per i thread POP3 e SMTP).

Dopo questo ciclo creo 2 thread (uno che si mette in attesa di una connessione SMTP e l'altro in attesa di una connessione POP3). Quando arriva una connessione, per esempio SMTP, il thread di ascolto SMTP crea un nuovo thread per gestire tale connessione (e quindi per accettare i comandi SMTP da parte del client) e poi termina. Nel frattempo il thread principale "si accorge" che uno dei thread di ascolto terminato e quindi ne verrà creato un altro che si rimetterà di nuovo in ascolto (a meno che non abbia già raggiunto il limite).

Siccome si tratta di un server che richiede un'interazione con l'utente (di tipo richiesta-risposta) anch'io non saprei cosa si intende per thread inattivo. Quindi la mia richiesta è più incentrata sul capire in base a cosa dovrei chiudere un thread/processo piuttosto che un altro quando vado a rileggere il file di configurazione.

WarDuck
06-04-2010, 17:27
Ci sono diverse strategie per gestire il multi-threading, ti posso fare l'esempio di quella che ho usato io per il p2p:

- pre-threading: crei N thread che si mettono in attesa di connessioni, per evitare di perdere tempo nel creare nuovi thread

Ad esempio, creo 5 thread iniziali, questa sarà la situazione:

THREAD_OCC = 0
THREAD_NUM = 5

Per abbreviare diciamo 0/5 (thread occupati / thread totali)

Quindi per 5 connessioni stai apposto, se il numero di thread occupati cresce "troppo", potresti usare una politica del tipo "raddoppia i thread".

Esempio:

Stai con 4/5 di thread occupati, arriva il 5°... 5/10 (5 thread occupati su 10 totali).

A questo punto quand'è che un thread è inattivo? Lo decidi te, ad esempio se non ti arrivano nuove connessioni per 2 minuti potresti cominciare a ridurre il numero di thread.

Ad esempio io avevo fatto che ogni 30 secondi 1 thread veniva terminato (è stata una mia scelta).

Quindi supponendo che non mi arrivano nuove connessioni:

inizio: 5/10 thread occupati
dopo 30 sec: 5/9
dopo 30 sec: 5/8
dopo 30 sec: 5/7
dopo 30 sec: 5/6 (ne lasci 1 libero per gestire un'altra eventuale connessione)

Quindi thread occupati si intendono thread che stanno attualmente gestendo la connessione.

Ah un inciso: potresti scegliere di tenere la connessione aperta per un certo periodo di tempo dopo aver ricevuto l'ultimo comando.

Infatti è qui che si decide la differenza tra un thread occupato ed uno non occupato.

Manugal
06-04-2010, 17:41
Ho capito. Quindi in realtà l'unico modo è basarmi sul tempo trascorso dall'ultima ricezione di un comando. Dovrò quindi implementare una sorta di timer. Ci proverò, grazie dell'aiuto.

Teo@Unix
06-04-2010, 17:43
ok, un thread principale più altri due che si mettono in ascolto.

Quindi i thread minimi sono 3.

Potresti definire una variabile ed un metodo per accedervi e per modificarla che definisce il massimo numero di connessioni (sia POP3 che SMTP).

Il tuo ipotetico tasto "reload conf file" rileggerà il file di configurazione e nel caso modificherà questo valore.

Quindi come hai detto, ogni volta che avrai bisogno di una nuova connessione controlli questo valore.

Per quanto riguarda i thread inattivi (immagino ti abbiano chiesto un'applicazione che elimini questi thread inattivi giusto?)
Bè di solito i client di posta non mantengono la connessione con il server ma la rieffettuano ogni volta. Quindi in questo caso non ci sarebbero mai thread inattivi. Naturalmente tu puoi fare diversamente. In questo caso i thread inattivi potrebbero essere le connessioni in TIME_WAIT, che non stanno facendo nulla e potrebbero essere benissimo terminate.

A seconda di cosa stai usando, ad esempio se usi linguaggi ad alto livello come C++ ci sarà un metodo del socket tipo socket.isTime_wait() o roba simile per vedere lo stato della connessione se è così lo termini e elimini tutti i thread possibili per tornare sotto il limite prefissato.

WarDuck
06-04-2010, 19:09
Ho capito. Quindi in realtà l'unico modo è basarmi sul tempo trascorso dall'ultima ricezione di un comando. Dovrò quindi implementare una sorta di timer. Ci proverò, grazie dell'aiuto.

In genere è quello che si fa. Il famoso "timeout" per intenderci.

Tipicamente il client manda un "keep alive" al server per mantenere la connessione attiva, ad esempio nel caso di un server POP3 si può mandare un comando NOOP.

Se il server si vede scadere il timeout può liberare le risorse, in quanto è molto probabile che il client sia crashato.

Manugal
06-04-2010, 21:00
Capito. Grazie mille.