2012-03-19 11 views
13

En mi aplicación, hay una io-hilo, que se dedica aLinux seleccionar() vs ppoll() vs pselect()

  1. datos de embalaje recibido de la aplicación de un protocolo personalizado
  2. envío el paquete de datos + protocolo personalizado sobre tcp/ip
  3. Recepción de datos + paquete de protocolo personalizado sobre tcp/ip
  4. Desenvolver el protocolo personalizado y entregar los datos a la aplicación.

La aplicación procesa los datos sobre un hilo diferente. Además, los requisitos dictan que el tamaño de la ventana no reconocida debe ser 1, es decir, debe haber solo un mensaje no confirmado pendiente en cualquier momento. Esto implica que si io-thread ha enviado un mensaje por el socket, no enviará más mensajes, hasta que oiga un acuse de recibo del receptor. La cadena de procesamiento de la aplicación se comunica con io-thread a través de la tubería. La aplicación debe cerrarse correctamente si alguien de la CLI de Linux escribe ctrl + C. Por lo tanto, teniendo en cuenta estos requisitos, he siguientes opciones

  1. Uso PPoll() en el socket y tuberías descriptores
  2. Uso select()
  3. Uso PMarque()

He siguientes preguntas

  1. La decisión entre select() y poll(). Mi aplicación solo trata con menos de 50 descriptores de archivos. ¿Está bien suponer que no habría diferencia si elijo seleccionar o sondear?

    1. Decisión entre select() y pselect(). Leí la documentación de Linux y dice sobre la condición de carrera entre las señales y select(). No tengo experiencia con las señales, ¿alguien puede explicar más claramente sobre la condición de carrera y seleccionar()? ¿Tiene algo que ver con que alguien presione Ctrl + C en la CLI y la aplicación no se detenga?

    2. Decisión entre pselect y ppoll()? Cualquier reflexión sobre uno frente al otro

Respuesta

20

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).

+0

"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

+0

@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

4

Entre (p) seleccionar y (p) Poll es una diferencia bastante sutil:

Para seleccionar, usted tiene que inicializar y rellenar los mapas de bits fd_set feos cada vez antes de llamar a select porque select los modifica in situ de una manera "destructiva". (la encuesta distingue entre los miembros .events y .revents en struct pollfd).

Después de seleccionar, todo el mapa de bits a menudo es escaneado (por personas/código) para eventos incluso si ni siquiera se mira la mayoría de los fds.

En tercer lugar, el mapa de bits solo puede tratar con fds cuyo número es inferior a cierto límite (implementaciones contemporáneas: en algún lugar entre 1024 ... 4096), que lo descarta en programas donde se pueden obtener fácilmente fds altos (a pesar de que es probable que los programas ya usen epoll en su lugar).

+2

Mi sensación es que 'select' ya no debería usarse en programas recién escritos, sino solo' poll' [o 'ppoll'] (al menos en sistemas como not Linuxes demasiado viejos donde tanto 'select' como' poll' son llamadas al sistema nativas). Creo que 'select' es solo para programas heredados ... –

+1

@BasileStarynkevitch Cita necesaria ... – paradigmatic

+0

http://www.kegel.com/c10k.html o simplemente el hecho de que el tamaño de' fd_set' limita el máximo descriptor de archivo mucho más estrictamente de lo que permite 'poll' –

0

La respuesta aceptada no es correcta frente a la diferencia entre select y pselect. Describe bien cómo puede surgir una condición de carrera entre sig-handler y select, pero es incorrecta en cómo utiliza pselect para resolver el problema. Se echa de menos el punto principal acerca de pselect, que es que espera que el descriptor de archivo o la señal estén listos. pselect regresa cuando cualquiera de estos está listo. Seleccione SOLO espera en el descriptor de archivo. Select ignora las señales. Vea esta publicación en el blog para un buen ejemplo de trabajo: https://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race