Sugeriría comenzando la comparación con select()
contra poll()
. Linux también proporciona pselect()
y ppoll()
; y el argumento const sigset_t *
adicional a pselect()
y ppoll()
(frente a select()
y poll()
) tiene el mismo efecto en cada "variante p", por así decirlo. Si no está utilizando señales, no tiene ninguna carrera para protegerse, por lo que la pregunta base es realmente acerca de la eficiencia y la facilidad de programación.
Mientras tanto, ya hay una respuesta de stackoverflow.com aquí: what are the differences between poll and select.
En cuanto a la carrera: una vez que empiezas a usar señales (por la razón que sea), aprenderás que, en general, un manejador de señales solo debe establecer una variable de tipo volatile sig_atomic_t
para indicar que la señal ha sido detectada. La razón fundamental de esto es que muchas llamadas a la biblioteca no son re-entrant, y se puede enviar una señal mientras está "en el medio de" tal rutina. Por ejemplo, simplemente imprimir un mensaje a una estructura de datos de estilo de secuencia como stdout
(C) o cout
(C++) puede provocar problemas de reentrada.
Suponga que tiene el código que utiliza una variable volatile sig_atomic_t flag
, tal vez para coger SIGINT
, algo como esto (véase también 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 ...
...
Ahora, en el cuerpo principal de su código, es posible que desee "correr hasta que se interrumpa":
while (!got_interrupted) {
... do some work ...
}
Esto está bien hasta que comience a tener que hacer llamadas que esperan a alguna entrada/salida, como select
o poll
. La acción "esperar" debe esperar esa E/S, pero también debe esperar a una interrupción SIGINT
. Si usted acaba de escribir:
while (!got_interrupted) {
... do some work ...
result = select(...); /* or result = poll(...) */
}
entonces es posible que la interrupción ocurrirá justo antes se llama select()
o poll()
, en lugar de después. En este caso, lo interrumpieron, y la variable got_interrupted
se establece, pero después de eso, comienza a esperar. Debería haber verificado la variable got_interrupted
antes de comenzar a esperar, no después.
Usted puede intentar escribir:
while (!got_interrupted) {
... do some work ...
if (!got_interrupted)
result = select(...); /* or result = poll(...) */
}
Esto reduce la "ventana de raza", porque ahora se podrá detectar la interrupción si sucede mientras estás en el código "hacer un trabajo"; pero todavía hay una carrera, porque la interrupción puede suceder justo después de prueba la variable, pero justo antes de el select-or-poll.
La solución es hacer que la "prueba, a continuación, esperar" secuencia "atómica", utilizando las propiedades de bloqueo de señal de sigprocmask
(o, en POSIX código, pthread_sigmask
roscados):
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 */
}
(el anteriormente el código en realidad no es tan bueno, está estructurado para la ilustración en lugar de la eficiencia; es más eficiente hacer la manipulación de la máscara de señal de forma ligeramente diferente, y colocar las pruebas "interrumpidas" de forma diferente).
Hasta que comience realmente necesidad de coger SIGINT
, sin embargo, sólo tiene que comparar select()
y poll()
(y si se inicia necesidad de un gran número de descriptores, algunas de las cosas basada en eventos como epoll()
es más eficaz que cualquiera de los dos).
"La razón fundamental de esto es que muchas llamadas a la biblioteca no son reentrantes" Entonces, ¿qué sucede si una señal se entrega mientras estamos en el medio de decir "leer". ¿Significa esto que nunca podremos usar read again? – kptlronyttcna
@kptlronyttcna: no, y aún así ligeramente, sí: no se puede llamar con seguridad, por ejemplo, 'fread' en stdin en un controlador de señal. Eso no significa que no puedas * nunca * invocar 'fread', solo que no puedes hacerlo en una variable compartida en un controlador de señal. Los detalles precisos de lo que puede hacer de manera segura y lo que no puede variar varían según la biblioteca, el sistema y otros detalles. (Como punto secundario, cuando 'leer' es una llamada al sistema, como en Linux y BSD y Mac, su comportamiento preciso depende del objeto subyacente del sistema de archivos. Los dispositivos" lentos "pueden devolver errores EINTR o lecturas cortas). – torek