2008-12-02 21 views
5

Si je voulais écrire:Comment détecter un appel Selector.wakeup

 
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. 

serait-il détecter correctement le réveil-appel?

Backgroundinformation:

J'écris un serveur qui la plupart du temps reçoit juste les paquets et les stocke dans un fichier. Très rarement l'application a besoin de s'envoyer un paquet spécial. Pour cela, il établit une connexion (à partir d'un autre thread) à la prise du serveur:

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

Le problème est que SelectableChannel.register() peut bloquer si le thread principal est déjà dans Selector.select(). Pour éviter cela, j'appelle Selector.wakeup() qui laisse le thread principal revenir prématurément de select(). Pour s'assurer que l'autre thread a la chance de compléter l'appel de registre, je devrais synchroniser le thread principal, mais je devrais le faire après tous les retour de select(). Si je pouvais détecter si elle revenait de select() à cause d'un appel de wakeup(), alors je pourrais l'optimiser pour juste ce cas. Donc, en théorie, l'extrait de code supérieur devrait fonctionner, mais je me demandais s'il ne le ferait que parce qu'il repose sur un comportement non spécifié?

Merci pour tous les conseils.

+0

Quelle est votre motivation pour vouloir éviter les verrous? Est-ce que vous êtes préoccupé par le temps d'exécution, ou appelez-vous select() dans de nombreux endroits et que vous voulez éviter la duplication de code? –

+0

Je suis inquiet au sujet du temps d'exécution, bien que cela puisse être un point discutable puisque select() et register() se synchronisent déjà sur les clés enregistrées. – BugSlayer

+0

D'accord, s'inquiéter de ceci est un exemple classique d'optimisation prématurée. À moins que vous ne parliez vraiment de tolérances de microsecondes, vous ne remarquerez aucun ralentissement. Je vais vomir une réponse en utilisant des verrous. –

Répondre

3

Je suppose que l'extrait proposé ne fonctionnerait pas du tout en principe, par les contrats de Selector#select() et Selector#selectedKeys(). De Selector:

  • Le set-clé sélectionnée est l'ensemble des touches telles que le canal de chaque clé a été détectée pour être prêt pour au moins l'une des opérations identifiées dans le jeu d'intérêt de la clé lors d'une opération de sélection préalable. Cet ensemble est renvoyé par la méthode selectedKeys.
public abstract int select(long timeout) 
       throws IOException 
    Returns: 
     The number of keys, possibly zero, whose ready-operation sets were 
     updated 

Comme je l'ai lu cela, la taille de l'ensemble selectedKeys doit toujours être égal au nombre retourné par select par définition. J'ai remarqué - comme vous pouvez l'avoir aussi - que certaines implémentations ne suivent pas tout à fait la documentation, et en fait selectedKeys renvoie toutes les clés avec des ensembles prêts à l'emploi mis à jour, même si elles n'étaient pas mises à jour pendant un appel select. Le seul autre indicateur que le select s'est réveillé en raison d'un appel à wakeup pourrait être que le nombre de clés est zéro; Cependant, l'une ou l'autre méthode ne serait pas fiable, au mieux.

La manière habituelle de gérer cela est, implicitement, le contrôle des accès concurrents. Je ne m'inquiéterais pas du temps d'exécution ici; c'est un exemple classique de premature optimization. À moins que vous n'ayez vraiment peur des tolérances de microsecondes à un chiffre, vous ne remarquerez aucun ralentissement - et si ce niveau de tolérance vous inquiète, un Selector ne sera pas suffisamment fiable pour vous.

Voici un exemple du mécanisme habituel pour cela, en utilisant un ReentrantLock pour accomplir la concurrence appropriée:

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(); 
    } 
} 
+0

Note: * ont été mis à jour *. Cela signifie seulement que le résultat 'select()' devrait être égal à la taille de l'ensemble de clés sélectionnées s'il était vide avant que vous n'appeliez 'select()'. Si vous ne l'effacez pas après chaque 'select()', ou si vous enlevez chaque 'SelectionKey' en même temps que vous le traitez, vous obtiendrez des résultats différents. Ce n'est pas une question d'implémentations différentes, c'est un bug d'application. – EJP

+0

@EJP Cela fait très longtemps, mais je crois que l'ensemble de clés sélectionnées était toujours vide avant l'appel de 'select()'. Au lieu de cela, le comportement était le suivant: a. commencer avec un ensemble vide, b. appel 'select()', renvoie 1, c. appel 'selectedKeys()', renvoie un ensemble de taille 2. –

0

Je ne comprends pas pourquoi votre code fonctionnerait en général. Pourquoi ne pas simplement vérifier un volatile après select?

+0

Je cherche une solution 'implicite', c'est-à-dire que je veux utiliser l'information disponible de toute façon sans aucun marqueur 'explicite' comme les variables booléennes. – BugSlayer

-1

Vous ne pouvez pas être sûr que la seule raison pour laquelle le sélecteur s'est réveillé était due à l'appel de réveil. Vous pouvez également avoir une activité de socket. Donc, vous devez faire en sorte que l'appelant de réveil fasse quelque chose comme définir un booléen volatile pour indiquer son désir d'attention. La boucle de sélection peut vérifier la valeur de ce booléen chaque fois qu'il se réveille.

+0

Oui, vous pouvez. S'il y avait une activité de socket, elle retournerait une valeur positive. – EJP

0

Si select() renvoie zéro, soit il a expiré ou il a été réveillé.