Comment détecter un appel Selector.wakeup
Question
Si j'écrivais:
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.
détecterait-il correctement l'appel de réveil?
Informations de base:
J'écris un serveur qui, la plupart du temps, ne reçoit que des paquets et les stocke dans un fichier. Très rarement, l'application a la nécessité de s'envoyer un paquet spécial. Pour cela, il établit une connexion (à partir d’un autre thread) au socket 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 se bloquer si le thread principal est déjà dans Selector.select (). Pour éviter cela, j'appelle Selector.wakeup (), qui permet au thread principal de revenir prématurément à select (). Pour m'assurer que l'autre thread a la possibilité de terminer l'appel register, je devrais synchroniser le fil principal, mais je le ferais après tous les retours de select (). Si je pouvais détecter si elle est retournée par select () à cause d'un appel à wakeup (), je pourrais alors l'optimiser pour ce cas uniquement.
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 vos remarques.
La solution
Je suppose que l'extrait de code proposé ne fonctionnerait pas du tout en principe, conformément aux contrats de Selector # select ()
et de Selector # selectedKeys ()
. De Sélecteur :
- Le jeu de clés sélectionné est le jeu de clés tel que le canal de chaque clé a été détecté comme étant prêt pour au moins une des opérations identifiées dans le jeu d'intérêt de la clé lors d'une opération de sélection antérieure. 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, la taille de l'ensemble selectedKeys
doit toujours être égale au nombre renvoyé par select
par définition. Comme vous le savez peut-être aussi, j’ai remarqué que certaines implémentations ne suivent pas tout à fait la documentation. En fait, selectedKeys
renvoie toutes les clés contenant des ensembles prêts à être mis à jour, même s'ils n'ont pas été mis à jour un appel à sélectionnez
. Le seul autre indicateur que la sélection a réveillé en raison d'un appel à réveil
pourrait être que le nombre de clés est égal à 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, comme implicite, par le contrôle de la simultanéité. Je ne m'inquiéterais pas du temps d'exécution ici; il s'agit d'un exemple classique de optimisation prématurée .
Sauf si vous vous inquiétez vraiment des tolérances à la microseconde à un chiffre, vous ne remarquerez aucun ralentissement. Si vous êtes inquiet à propos de ce niveau de tolérance, un Selector
ne sera pas fiable. assez pour vous de toute façon.
Voici un exemple du mécanisme habituel, utilisant un ReentrantLock
pour réaliser la concurrence simultanée 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();
}
}
Autres conseils
Je ne comprends pas pourquoi votre code fonctionnerait en général.
Pourquoi ne pas simplement vérifier un volatile
après select
?
Si select ()
renvoie zéro, le délai a expiré ou il a été réveillé.
Vous ne pouvez pas vraiment être sûr que la seule raison pour laquelle le sélecteur s'est réveillé était due au réveil. Vous pouvez également avoir une activité de socket.
Ainsi, vous devez faire en sorte que l'appelant de réveil fasse quelque chose comme définir un booléen volatil pour indiquer son désir d'attirer l'attention. La boucle de sélection peut vérifier la valeur de ce booléen à chaque réveil.