Domanda

Se dovessi scrivere:

int selectedChannels = selector.select();
Set selectedKeys = selector.selectedKeys();
if ( selectedChannels != selectedKeys.size() ) {
    // Selector.select() returned because of a call to Selector.wakeup()
    // so do synchronization.
}
// Continue with handling selected channels.

rileverà correttamente la sveglia?

Backgroundinformation:

Sto scrivendo un server che il più delle volte riceve solo pacchetti e li archivia in un file. Molto raramente l'applicazione ha la necessità di inviarsi un pacchetto speciale. Per questo avvia una connessione (da un thread diverso) al socket del server:

SocketChannel channel = SocketChannel.open();
channel.configureBlocking( false );
channel.connect( new InetSocketAddress( InetAddress.getLocalHost(), PORT ));
selector.wakeup();
SelectionKey key = channel.register( selector, SelectionKey.OP_CONNECT );

Il problema è che SelectableChannel.register () potrebbe bloccare se il thread principale è già in Selector.select (). Per evitare che ciò accada, sto chiamando Selector.wakeup () che consente al thread principale di tornare prematuramente da select (). Per essere sicuro che l'altro thread abbia la possibilità di completare la chiamata di registro, dovrei sincronizzare il thread principale, ma dovrei farlo dopo ogni ritorno da select (). Se potessi rilevare se è tornato da select () a causa di una chiamata wakeup (), allora potrei ottimizzarlo solo per questo caso.

Quindi, in teoria, lo snippet di codice principale dovrebbe funzionare, ma mi chiedevo se lo farebbe solo, perché si basa su un comportamento non specificato?

Grazie per eventuali suggerimenti.

È stato utile?

Soluzione

Immagino che lo snippet proposto non funzionasse affatto in linea di principio, secondo i contratti di Selector # select () e Selector # selectedKeys () . Da Selector :

  
      
  • Il set di chiavi selezionato è l'insieme di chiavi in ??modo tale che ciascun canale di chiave è stato rilevato pronto per almeno una delle operazioni identificate nell'interesse della chiave durante un'operazione di selezione precedente. Questo set viene restituito dal metodo selectedKeys.
  •   
public abstract int select(long timeout)
                throws IOException
    Returns:
        The number of keys, possibly zero, whose ready-operation sets were
        updated

Come ho letto, la dimensione del set selectedKeys dovrebbe sempre essere uguale al numero restituito da select per definizione. Ho notato - come forse anche tu - che alcune implementazioni non seguono del tutto la documentazione, e in effetti selectedKeys restituisce tutte le chiavi con set pronti per l'uso aggiornati, anche se non sono stati aggiornati durante una chiamata a seleziona . L'unico altro indicatore che la selezione si è svegliata a causa di una chiamata a wakeup potrebbe essere che il numero di tasti è zero; tuttavia entrambi i metodi sarebbero inaffidabili, nella migliore delle ipotesi.

Il solito modo di gestirlo è, come implicito, attraverso il controllo della concorrenza. Non mi preoccuperei dei tempi di esecuzione qui; questo è un classico esempio di ottimizzazione prematura .

A meno che tu non sia davvero preoccupato per le tolleranze dei microsecondi a una cifra, non noterai alcun rallentamento - e se sei preoccupato per quel livello di tolleranza, un Selector non sarà affidabile abbastanza per te comunque.

Ecco un esempio del solito meccanismo per questo, usando un ReentrantLock per ottenere la concorrenza appropriata:

ReentrantLock selectorGuard;
Selector selector;

private void doSelect() {
    // Don't enter a select if another thread is in a critical block
    selectorGuard.lock();
    selectorGuard.unlock();

    selector.select();
    Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();

    while(keyIter.hasNext()) {

        SelectionKey key = keyIter.next();
        keyIter.remove();

        // Process key
    }
}

private void addToSelector() {

    // Lock the selector guard to prevent another select until complete
    selectorGuard.lock();

    try {
        selector.wakeup();

        // Do logic that registers channel with selector appropriately

    } finally {
        selectorGuard.unlock();
    }
}

Altri suggerimenti

Non capisco perché il tuo codice funzioni in generale.

Perché non controllare solo un volatile dopo selezionare ?

Se select () restituisce zero, è scaduto o è stato svegliato.

Non puoi davvero essere sicuro che l'unico motivo per cui il selettore si è svegliato fosse dovuto alla sveglia. Potresti anche avere attività socket.

Quindi, devi fare in modo che chi chiama al risveglio faccia qualcosa come impostare un valore booleano volatile per indicare il suo desiderio di attenzione. Il ciclo di selezione può controllare il valore di questo valore booleano ogni volta che si sveglia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top