2012-10-10 79 views
8

Tengo un Thread subclasificado con un Selector privado y un método register(SelectableChannel channel, ...) público que permite que otros subprocesos registren canales en el selector.Selector de NIO: cómo registrar correctamente el nuevo canal al seleccionar

como respondida here, register() bloques del canal durante el selector de select()/select(long timeout) por lo que necesitamos wakeup() el selector.

Mi hilo selecciona indefinidamente (a menos que se interrumpa) y realmente logra entrar en la siguiente selección antes de llamar al register() del canal. Así que pensé usar un bloqueo simple con bloques synchronized para asegurar que el register() ocurra primero.

el código: (código irrelevante eliminado para facilitar la lectura)

public class SelectorThread extends Thread { 
    ... 

    public void register(SelectableChannel channel, Attachment attachment) throws IOException { 
    channel.configureBlocking(false); 
    synchronized (this) { // LOCKING OCCURS HERE 
     selector.wakeup(); 
     channel.register(selector, 
         SelectionKey.OP_READ, 
         attachment); 
    } 
    } 

    @Override 
    public void run() { 
    int ready; 
    Set<SelectionKey> readyKeys; 
    while (!isInterrupted()) { 
     synchronized (this) {} // LOCKING OCCURS HERE 

     try { 
     ready = selector.select(5000); 
     } catch (IOException e) { 
     e.printStackTrace(); 
     continue; 
     } 

     if (ready == 0) { 
     continue; 
     } 

     readyKeys = selector.selectedKeys(); 

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

     if (!key.isValid()) { 
      continue; 
     } 

     if (key.isReadable()) { 
      ... 
     } 
     } 
    } 
    } 
} 

Este simple bloqueo permite register() ocurra antes de que el hilo continúa con el siguiente bucle de selección. Por lo que he probado, esto funciona como se supone.

Preguntas: ¿Es esa una "buena" manera de hacerlo o hay serias desventajas en eso? ¿Sería mejor usar una Lista o Cola (como se sugiere here) para almacenar los canales para el registro, o un bloqueo más sofisticado como this en su lugar? ¿Cuáles serían los Pros/Contras de eso? ¿O hay formas "aún mejores"?

+0

Posible duplicación de [bloques de subprocesos de Java mientras se registra el canal con selector mientras se llama a select(). ¿Qué hacer?] (Https://stackoverflow.com/questions/1057224/java-thread-blocks-while-registering-channel-with-selector-while-select-is-cal) – Flow

Respuesta

3

Estoy realmente sorprendido de que la adquisición de bloqueo con el bloque vacío no se elimine en tiempo de compilación. Genial que funciona. Quiero decir que funciona, es preventivo, no es el enfoque más bonito, pero funciona. Es mejor que un modo de suspensión, ya que es predecible y, dado que utiliza la llamada de activación, sabe que se realizará un progreso según sea necesario en lugar de hacerlo en una actualización periódica si se basó exclusivamente en el tiempo de espera de selección.

La desventaja principal de este enfoque es que usted está diciendo que las llamadas para registrarse son superiores a cualquier otra cosa, incluso para las solicitudes de servicio. Lo cual puede ser cierto en su sistema, pero generalmente este no es el caso, diría que este es un problema posible. Un problema menor, que es pensar más en el futuro, es que se bloquea el SelectorThread, que es una especie de objeto más grande en este caso. No está mal, aunque no es genial a medida que se expande, este bloqueo solo tendrá que documentarse y tenerse en cuenta siempre que otros clientes usen esta clase. Personalmente, me gustaría hacer otro bloqueo por completo para evitar peligros futuros imprevistos.

Personalmente, me gustan las técnicas de colas. Asignan roles a sus hilos, como un maestro y trabajadores de esta manera.Mientras que todos los tipos de control suceden en el maestro, como después de cada selección, se seleccionan más registros de una cola, se borran y se agotan las tareas de lectura, se manejan los cambios en la configuración general de la conexión (se desconecta, etc.) ... La concurrencia "bs" modelo parece aceptar este modelo bastante bien y es un modelo bastante estándar. No creo que sea algo malo, ya que hace que el código sea menos hacky, más comprobable y más fácil de leer. Solo toma un poco más de tiempo para escribir.

Aunque lo admitiré, ha pasado mucho tiempo desde la última vez que escribí esto, hay otras bibliotecas por ahí que se encargan de hacer las colas por usted.

Grizzly Nio Framework un poco viejo, la última vez que lo usé, el runloop principal no estaba mal. Configura muchas colas para ti.

Apache Mina Similar en que proporciona un marco de espera.

Pero al final depende de en lo que esté trabajando.

  • ¿Es un proyecto de un solo hombre jugar con el marco?
  • ¿Es un código de producción en el que desea vivir por años?
  • ¿Es un código de producción en el que está iterando?

A menos que esté pensando en utilizar esto como un elemento central de un servicio que está proporcionando a los clientes, diría que su enfoque es bueno. Puede tener problemas de mantenimiento a largo plazo.

+0

Muy buena entrada para mí, gracias. No creo que el registro sea mejor que el servicio, ya que la activación solo afecta las selecciones en espera, no el resto del ciclo. En cuanto al bloqueo, ¿sería mejor bloquear un objeto privado simple en su lugar? (un objeto únicamente para bloquear llamadas de registro) No espero que se registren grandes cantidades de canales al mismo tiempo, por lo que creo que una cola de registro es demasiado. Pero me gusta la idea y podría implementarla en el futuro. – riha

+0

RE: selecciona y da servicio, sí, eso es lo que quiero decir. Al igual que pasará un ciclo vacío solo para hacer frente al registro que acaba de suceder. No es horrible, pero requiere el bloqueo adicional y se soluciona de inmediato. En los sistemas de colas, generalmente tiene colas que no bloquean y que elevan la necesidad de estos bloqueos. –

+0

RE: los bloqueos, sí, haría un bloqueo interno que es solo para tratar con el selector, personalmente. Pero sí, parece que eres bueno. –

4

Simplemente trate Selector etc como no seguro para subprocesos, realice todas las acciones relacionadas de selección en el mismo subproceso, como sugirió Darron.

El modelo de concurrencia del selector NIO es mierda. Debo decirlo, porque es una gran pérdida de tiempo para todos los que intentan estudiarlo. Al final, la conclusión es, olvídalo, no es para uso concurrente.

2

Todo lo que necesita es un despertador() antes del registro(), y, en el ciclo de selección, un breve descanso antes de continuar si 'listo' es cero, para dar a register() la oportunidad de ejecutar. Sin sincronización adicional: ya es suficientemente malo; no lo empeore. No soy partidario de estas colas de cosas para registrar, cancelar, cambiar operaciones de interés, etc.: simplemente secuencializan cosas que realmente se pueden hacer en paralelo.

+0

¿Podría explicar cómo registrarse? los canales realmente se pueden hacer en paralelo? Si el registro solo se puede hacer en el mismo hilo que también selecciona, ¿cómo podría ser eso paralelo? Además, ¿qué consideras un "sueño corto"? 1 ms? 10 ms? 100 ms? – riha

+1

@riha ¿Eh? No he dicho que registrar canales solo se puede hacer en el hilo select(). Se puede hacer en un hilo separado, a través de la técnica que he descrito aquí, en respuesta a su pregunta sobre precisamente ese tema. Seguramente eso está claro? Re el sueño, 100ms debería ser suficiente. – EJP

+0

Ah, lo leí [aquí] (http://stackoverflow.com/a/2179612/589008), lo siento. ¿Pero no nos estamos topando con los bloqueos impuestos por el selector al registrar varios canales "al mismo tiempo"? Si ese es el caso, ¿dónde está el beneficio sobre el uso de una cola? – riha

Cuestiones relacionadas