2012-03-30 11 views
8

Mi Java NIO selector se implementa utilizando select() por lo que se bloquea hasta que cualquiera de estos síntomas:Java Selector NIO seleccionar() devuelve 0 si bien los canales están dispuestos

  1. un canal registrado está listo
  2. es wakeup() ' ed
  3. el hilo se interrumpe

partir de esto, hice algunas suposiciones sobre el caso en que select() devuelve 0:

  • que debe haber sido la razón o 2. 3.
  • selectedKeys() debe devolver un vacío ResultSet
  • No necesito llamar selectedKeys() y puede continuar a la siguiente iteración del bucle, donde se llamará select() de nuevo

Sin embargo, encontré situaciones donde select() devolvió 0 aunque hay un canal listo. selectedKeys() devuelve un Set con 1 SelectionKey como se esperaba.

Incluso varias llamadas a select() siempre devolverán 0 hasta que se haya procesado el canal y se haya eliminado SelectionKey. Esta situación básicamente termina en un bucle sin fin como select() no bloquea al instante, pero siempre devolverá 0.

código simplificado:

Selector selector = Selector.open(); 

SocketChannel channel; 

for (...) { // for each node 
    // Create and connect channels... 
    ... 

    channel.configureBlocking(false); 
    channel.register(selector, SelectionKey.OP_READ, someRelatedObject); 
} 

int ready; 
Set<SelectionKey> readyKeys; 
while (true) { 
    ready = selector.select(); 
    readyKeys = selector.selectedKeys(); 

    System.out.println("Ready channels: " + ready); 
    System.out.println("Selected channels: " + readyKeys.size()); 

    if (ready == 0) { 
    continue; 
    } 

    for (SelectionKey key : readyKeys) { 
    if (key.isValid() && key.isReadable()) { 
     // Take action... 
    } 

    readyKeys.remove(key); 
    } 
} 

¿Por qué select() retorno 0 si bien existe un canal listo? ¿Cuál es la forma sugerida de lidiar con esto?

EDIT:

Cambiar esta:

for (SelectionKey key : readyKeys) { 
    if (key.isValid() && key.isReadable()) { 
     // Take action... 
    } 

    readyKeys.remove(key); 
    } 

a este

for (SelectionKey key : readyKeys) { 
    readyKeys.remove(key); 

    if (key.isValid() && key.isReadable()) { 
     // Take action... 
    } 
    } 

resuelto el problema. En algunos casos, el código sería continue el for bucle antes de remove() ing la clave.

EDIT 2:

Sólo recientemente se supo que mi foreach bucle sobre las claves seleccionadas conjunto es malo. foreach usa el iterador del conjunto. Modificar una colección directamente (no a través de los métodos del iterador) mientras se itera sobre ella puede generar un comportamiento "arbitrario, indeterminado".

El conjunto de claves seleccionado puede proporcionar un iterador a prueba de fallas. Fail-fast iteradores detectan tales modificaciones y arrojan un ConcurrentModificationException en la siguiente iteración.Por lo tanto, modificar el conjunto en un foreach tiene el riesgo de comportamiento no determinista o puede causar excepciones, dependiendo de la implementación del iterador.

Solución: no utilice foreach. Use el iterador y elimine la clave a través del iterator.remove().

Iterator<SelectionKey> iterator; 
SelectionKey key; 
while (true) { 
    // ... 

    iterator = selector.selectedKeys().iterator(); 
    while (iterator.hasNext()) { 
    key = iterator.next(); 
    iterator.remove(); 
    // ... 
    } 
} 

Respuesta

7

select() devuelve el número de claves que han cambiado . Entonces, si una clave ya estaba lista antes de la llamada select(), podría devolver 0 pero selectedKeys podría estar no vacío.

+2

Posiblemente si una clave estaba lista y no se eliminó? – riha

1

Como ha indicado, es porque no eliminó la clave seleccionada del conjunto seleccionado.

Como por lo general quieren eliminar todos claves seleccionadas después de procesar el conjunto, sólo puede llamar a clear() en ese set después de su bucle, que puede ser un foreach o cualquier tipo de bucle que desea:

Set<SelectionKey> readyKeys = selector.selectedKeys(); 
for (SelectionKey k : readyKeys) { 
    // Process k 
} 
readyKeys.clear(); 

De esta forma, puede usar cualquier tipo de ciclo que desee ("regular" for o iteradores) y asegúrese de que se eliminen todas las teclas, no importa lo que haga dentro del for (incluido el problema continue). Dependiendo de lo que el iterator.remove() haga internamente, podría ser más eficiente llamar al clear() una vez en lugar de varios iterator.remove() (aunque eso probablemente sería una microoptimización).

+0

No responde la pregunta: "¿Por qué' select() 'devuelve 0 aunque hay un canal listo?" – EJP

+0

@EJP tienes razón, lo he corregido. ¿Será suficiente para eliminar tu voto negativo? – Matthieu

+0

¿Cuál es la ventaja de eliminar instantáneamente cada 'SelectionKey' a través de' iterator.remove() '? – riha

Cuestiones relacionadas