Tra (p)select e (p)poll c'è una differenza piuttosto sottile:
Per select, devi inizializzare e popolare le brutte bitmap fd_set ogni volta prima di chiamare select perché select le modifica sul posto in modo "distruttivo". (il sondaggio distingue tra .events
e .revents
membri in struct pollfd
).
Dopo la selezione, l'intera bitmap viene spesso scansionata (da persone/codice) alla ricerca di eventi anche se la maggior parte degli fds non viene nemmeno guardata.
In terzo luogo, la bitmap può gestire solo fd il cui numero è inferiore a un certo limite (implementazioni contemporanee:da qualche parte tra 1024..4096), il che lo esclude nei programmi in cui è possibile raggiungere facilmente fd elevati (nonostante che tali programmi possano usa già invece epoll).
Suggerirei di iniziare il confronto con select()
contro poll()
. Linux fornisce anche entrambi pselect()
e ppoll()
; e l'extra const sigset_t *
argomento a pselect()
e ppoll()
(rispetto a select()
e poll()
) ha lo stesso effetto su ciascuna "variante p", per così dire. Se non stai usando i segnali, non hai nessuna gara da cui proteggerti, quindi la domanda di base riguarda davvero l'efficienza e la facilità di programmazione.
Nel frattempo c'è già una risposta di stackoverflow.com qui:quali sono le differenze tra poll e select.
Per quanto riguarda la gara:una volta che inizi a utilizzare i segnali (per qualsiasi motivo), imparerai che in generale un gestore di segnali dovrebbe semplicemente impostare una variabile di tipo volatile sig_atomic_t
per indicare che il segnale è stato rilevato. La ragione fondamentale di ciò è che molte chiamate in biblioteca non sono rientranti e un segnale può essere inviato mentre sei "nel mezzo" di una tale routine. Ad esempio, semplicemente stampando un messaggio in una struttura di dati in stile stream come stdout
(C) o cout
(C++) può portare a problemi di rientro.
Supponi di avere un codice che utilizza un volatile sig_atomic_t flag
variabile, forse per catturare SIGINT
, qualcosa del genere (vedi anche http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):
volatile sig_atomic_t got_interrupted = 0;
void caught_signal(int unused) {
got_interrupted = 1;
}
...
struct sigaction sa;
sa.sa_handler = caught_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGINT, &sa, NULL) == -1) ... handle error ...
...
Ora, nel corpo principale del tuo codice, potresti voler "eseguire finché non viene interrotto":
while (!got_interrupted) {
... do some work ...
}
Questo va bene fino a quando non inizi a dover effettuare chiamate che attendono alcuni input/output, come select
o poll
. L'azione "wait" deve attendere quell'I/O, ma anche deve attendere un SIGINT
interrompere. Se scrivi solo:
while (!got_interrupted) {
... do some work ...
result = select(...); /* or result = poll(...) */
}
allora è possibile che l'interruzione si verifichi poco prima chiami select()
o poll()
, piuttosto che dopo. In questo caso, sei stato interrotto e la variabile got_interrupted
viene impostato, ma dopo inizi ad aspettare. Avresti dovuto controllare il got_interrupted
variabile prima che iniziassi ad aspettare, non dopo.
Puoi provare a scrivere:
while (!got_interrupted) {
... do some work ...
if (!got_interrupted)
result = select(...); /* or result = poll(...) */
}
Questo riduce la "finestra di gara", perché ora rileverai l'interruzione se si verifica mentre sei nel codice "fai un po' di lavoro"; ma c'è ancora una corsa, perché l'interruzione può avvenire subito dopo si verifica la variabile, ma subito prima seleziona-o-sondaggio.
La soluzione è rendere "atomica" la sequenza "test, then wait", utilizzando le proprietà di blocco del segnale di sigprocmask
(o, nel codice thread POSIX, pthread_sigmask
):
sigset_t mask, omask;
...
while (!got_interrupted) {
... do some work ...
/* begin critical section, test got_interrupted atomically */
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
if (sigprocmask(SIG_BLOCK, &mask, &omask))
... handle error ...
if (got_interrupted) {
sigprocmask(SIG_SETMASK, &omask, NULL); /* restore old signal mask */
break;
}
result = pselect(..., &omask); /* or ppoll() etc */
sigprocmask(SIG_SETMASK, &omask, NULL);
/* end critical section */
}
(il codice sopra in realtà non è eccezionale, è strutturato per l'illustrazione piuttosto che per l'efficienza:è più efficiente eseguire la manipolazione della maschera del segnale in modo leggermente diverso e posizionare i test "è stato interrotto" in modo diverso).
Fino a quando non inizi effettivamente a dover catturare SIGINT
, tuttavia, devi solo confrontare select()
e poll()
(e se inizi ad aver bisogno di un gran numero di descrittori, alcune delle cose basate sugli eventi come epoll()
è più efficiente di entrambi).