2008-12-02 14 views
5

erkennen, wenn ich schreiben würde:Wie ein Selector.wakeup Anruf

 
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. 

wäre es richtig erkennen Wakeup-Call?

Hintergrundinformation:

ich einen Server zu schreiben, die die meiste Zeit nur Pakete und speichert sie in einer Datei empfängt. Sehr selten muss die Anwendung ein spezielles Paket senden. Hierzu initiiert er eine Verbindung (von einem anderen Thread) an den Server-Socket:

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

Das Problem ist, dass SelectableChannel.register() blockieren könnte, wenn der Haupt-Thread bereits in Selector.select ist(). Um dies zu verhindern, rufe ich Selector.wakeup() auf, wodurch der Haupt-Thread vorzeitig von select() zurückkehrt. Um sicherzustellen, dass der andere Thread die Möglichkeit hat, den Register-Aufruf abzuschließen, müsste ich den Haupt-Thread synchronisieren, aber ich würde es tun müssen, nachdem alle von select() zurückkehren. Wenn ich feststellen könnte, ob es aufgrund eines wakeup() -Aufrufs von select() zurückgegeben wurde, könnte ich es nur für diesen Fall optimieren.

in der Theorie also das Top-Code-Snippet sollte funktionieren, aber ich habe mich gefragt, ob es nur so tun würde, weil es auf einem unbestimmtes Verhalten beruht?

Danke für Hinweise.

+0

Was ist Ihre Motivation für die Vermeidung von Sperren? Machen Sie sich Sorgen über die Ausführungszeit oder rufen Sie an vielen Stellen select() auf und möchten Code-Duplikation vermeiden? –

+0

Ich mache mir Sorgen über die Ausführungszeit, obwohl dies ein strittiger Punkt sein könnte, da select() und register() bereits auf den registrierten Schlüsseln synchronisieren. – BugSlayer

+0

Einverstanden, sich darüber Gedanken zu machen, ist ein klassisches Beispiel für vorzeitige Optimierung. Wenn Sie nicht wirklich über Toleranzen von einer Mikrosekunde sprechen, werden Sie keine Verlangsamung bemerken. Ich werde eine Antwort mit Sperren werfen. –

Antwort

3

Ich würde vermuten, dass das vorgeschlagene Snippet überhaupt nicht funktionieren würde, nach den Verträgen von Selector#select() und Selector#selectedKeys(). Von Selector:

  • Der ausgewählten Schlüsselsatz ist der Satz von Tasten, so dass jeder Kanals Schlüssel wurde für mindestens eine der Operationen in dem Schlüssel Interesse Satz während einer vorherigen Auswahloperation identifizierten bereit sein detektiert. Dieser Satz wird von der selectedKeys-Methode zurückgegeben.
public abstract int select(long timeout) 
       throws IOException 
    Returns: 
     The number of keys, possibly zero, whose ready-operation sets were 
     updated 

Als ich das las, die Größe des selectedKeys Satz immer die von select definitionsgemäß zurück Zahl entsprechen sollte. Ich habe bemerkt - wie Sie vielleicht auch haben -, dass einige Implementierungen nicht ganz der Dokumentation folgen, und tatsächlich gibt selectedKeys alle Schlüssel mit aktualisierten betriebsbereiten Sets zurück, auch wenn sie während eines Anrufs auf select nicht aktualisiert wurden. Der einzige andere Indikator, dass die Auswahl aufgrund eines Anrufs auf wakeup aufgewacht ist, könnte sein, dass die Anzahl der Schlüssel Null ist; jedoch wäre jede Methode bestenfalls unzuverlässig.

Der übliche Weg, um dies zu handhaben, ist, wie impliziert, durch Nebenläufigkeitskontrolle. Ich würde mir hier keine Gedanken über die Ausführungszeit machen; Dies ist ein klassisches Beispiel für premature optimization.

Es sei denn, Sie sind wirklich besorgt über Mikro-Sekunden-Toleranzen im einstelligen Bereich, Sie werden keine Verlangsamung bemerken - und wenn Sie sich Sorgen über diese Toleranzgrenze machen, ist ein Selector sowieso nicht zuverlässig genug für Sie.

Hier ist ein Beispiel für den üblichen Mechanismus hierfür eine ReentrantLock mit dem entsprechenden Gleichzeitigkeit zu erreichen:

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

Hinweis: * wurden aktualisiert *. Das bedeutet nur, dass das 'select()' Ergebnis der Größe des ausgewählten Schlüsselsatzes entsprechen sollte, wenn es leer war, bevor Sie 'select()' aufgerufen haben. Wenn Sie es nicht nach jedem 'select()' löschen oder abwechselnd jeden 'SelectionKey' während der Bearbeitung entfernen, erhalten Sie andere Ergebnisse. Dies ist keine Frage von verschiedenen Implementierungen, es ist ein Anwendungsfehler. – EJP

+0

@EJP Es war eine sehr lange Zeit, aber ich glaube, dass der ausgewählte Schlüsselsatz vor dem Aufruf von 'select()' immer leer war. Stattdessen war das Verhalten wie folgt: a. Beginne mit einem leeren Satz, b. rufe 'select()' auf, gibt 1, c zurück. rufe 'selectedKeys()' auf, gibt einen Satz der Größe 2 zurück. –

0

Ich verstehe nicht, warum Ihr Code im Allgemeinen funktionieren würde.

Warum prüfen nicht nur ein volatile nach select?

+0

Ich suche nach einer 'impliziten' Lösung, was bedeutet, dass ich die Information, die sowieso verfügbar ist, ohne irgendwelche 'expliziten' Markierungen wie booleschen Variablen benutzen will. – BugSlayer

-1

Sie können nicht wirklich sicher sein, dass der einzige Grund, dass der Wähler auf den Weckruf durch aufwachten war. Möglicherweise haben Sie auch Socket-Aktivität.

So müssen Sie den Anrufer von Wakeup auch etwas tun, um wie ein flüchtiges boolean Einstellung ihren Wunsch nach Aufmerksamkeit anzuzeigen. Die Selektorschleife kann den Wert dieses Boolean bei jedem Aufwachen überprüfen.

+0

Ja, Sie können. Wenn Socket-Aktivität vorhanden wäre, würde es einen positiven Wert zurückgeben. – EJP

0

Wenn select() Null zurückgibt, entweder abgelaufen oder es wurde geweckt.