Java-NIO - Memory-mapped-Dateien
Ich kam vor kurzem in diesem Artikel, die eine nette Einführung in die memory-mapped-Dateien und wie kann Sie geteilt werden zwischen zwei Prozessen. Hier ist der code für einen Prozess, der liest in der Datei:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMapReader {
/**
* @param args
* @throws IOException
* @throws FileNotFoundException
* @throws InterruptedException
*/
public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
FileChannel fc = new RandomAccessFile(new File("c:/tmp/mapped.txt"), "rw").getChannel();
long bufferSize=8*1000;
MappedByteBuffer mem = fc.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
long oldSize=fc.size();
long currentPos = 0;
long xx=currentPos;
long startTime = System.currentTimeMillis();
long lastValue=-1;
for(;;)
{
while(mem.hasRemaining())
{
lastValue=mem.getLong();
currentPos +=8;
}
if(currentPos < oldSize)
{
xx = xx + mem.position();
mem = fc.map(FileChannel.MapMode.READ_ONLY,xx, bufferSize);
continue;
}
else
{
long end = System.currentTimeMillis();
long tot = end-startTime;
System.out.println(String.format("Last Value Read %s , Time(ms) %s ",lastValue, tot));
System.out.println("Waiting for message");
while(true)
{
long newSize=fc.size();
if(newSize>oldSize)
{
oldSize = newSize;
xx = xx + mem.position();
mem = fc.map(FileChannel.MapMode.READ_ONLY,xx , oldSize-xx);
System.out.println("Got some data");
break;
}
}
}
}
}
}
Habe ich allerdings ein paar Kommentare/Fragen zu diesem Ansatz:
Wenn wir führen den Leser nur eine leere Datei, ich.e führen Sie
long bufferSize=8*1000;
MappedByteBuffer mem = fc.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
long oldSize=fc.size();
Diese Zuordnung 8000 bytes, die jetzt erweitern Sie die Datei. Der Puffer, das gibt ein limit von 8000 und eine position von 0, daher kann der Leser gehen Sie und Lesen Sie leere Daten. Nachdem dies passiert ist, den Leser zu stoppen, als currentPos == oldSize
.
Angeblich jetzt der Schriftsteller kommt in (code weggelassen, da die meisten es unkompliziert ist und auf die verwiesen werden kann, die von der website) - es verwendet die selbe Puffer-Größe, so schreibt er ersten 8000 Byte, dann reservieren Sie einen anderen 8000, die Erweiterung der Datei. Nun, wenn wir annehmen, dieser Prozess hält an diesem Punkt, und wir gehen zurück an das Lesegerät, dann wird der Leser sieht die neue Größe der Datei und ordnet den Rest (also von 8000 bis 1600) und fängt an zu Lesen auch das Lesen in einem anderen Müll.
Ich bin ein bisschen verwirrt, ob es ein warum zu synchronisieren diese beiden Operationen. Soweit ich es sehe, ist jeder Aufruf zur map
verlängern könnte die Datei mit einer leeren Puffer (mit Nullen gefüllt) oder der Schriftsteller könnte nur die Datei erweitert, hat aber nichts mehr geschrieben, in der es noch nicht...
Ich weiß nicht, was du damit meinst, ob es ein warum zu synchronisieren', aber die Eröffnung viele memory-mapped-Dateien, oder das gleiche mehrmals, ist eine sehr schlechte Idee, jedenfalls für die garbage-collection Gründen, da es keine genau definierte Zeit, die das Gedächtnis betreffenden freigegeben werden können. Und es gibt keinen besonderen Vorteil für die Kartierung in kleinen Mengen wie 8k: Sie kann genauso gut mit buffered streams, die so viel Pufferung von Standard -, und keine dieser malarkey, was zu tun ist, wenn die Datei erweitert. Die zugeordneten Dateien sind am besten, wenn verwendet auf eine sehr kleine Zahl, wie etwa eins, von sehr großen Dateien.
OK, habe es - öffnen einer großen Datei. Noch, dies ist der Mittelwert für IPC, so möchte ich wissen, wie das erreicht werden kann, i..e ein Prozeß schreibt, der andere liest, aber in einer Weise, dass wir wissen, dass der andere Prozess tatsächlich schrieb sth, bevor wir Lesen können. Dies ist die Synchronisierung von ich spreche
Es ist eine Datei, nicht ein Rohr. Mithilfe von mmap() allein wird nicht zulassen, Sie zu synchronisieren. Der Beispielcode stellt (hässlich) busy-polling.
InformationsquelleAutor Bober02 | 2014-03-03
Du musst angemeldet sein, um einen Kommentar abzugeben.
Gibt es mehrere Möglichkeiten.
Lassen die Schriftsteller erwerben eine exklusive
Lock
auf die region, die noch nicht geschrieben worden. Die Sperre, wenn schon alles geschrieben wurde. Dies ist kompatibel mit jeder anderen Anwendung, die Sie auf das system, aber es erfordert, dass der Leser intelligent genug sein, um erneut über Fehler liest, es sei denn, Sie kombinieren Sie es mit einer der anderen MethodenVerwenden Sie einen anderen Kommunikationskanal, wie z.B. eine pipe oder ein socket oder eine Datei Metadaten channel zu lassen, die Schriftsteller erzählen, die den Leser über das fertige schreiben.
Schreiben an einer Stelle in der Datei einen speziellen marker (als Teil des Protokolls) erzählen, über die geschriebenen Daten, z.B.
FileLock
auf dem Kanal?Ja, ich meine
FileLock
.InformationsquelleAutor Holger
Ich tun eine Menge Arbeit mit der memory-mapped-Dateien, die für die prozessübergreifende Kommunikation. Ich würde nicht empfehlen Holger ' s #1 oder #2, aber seine #3 ist das, was ich Tue. Aber ein wichtiger Punkt ist vielleicht, dass ich immer nur die Arbeit mit einem einzelnen Schriftsteller, die Dinge noch komplizierter, wenn Sie haben mehrere Autoren.
Dem start der Datei wird ein header-Abschnitt, mit was auch immer-header-Variablen, die Sie brauchen, am wichtigsten ist ein Zeiger auf das Ende der geschriebenen Daten. Der Schriftsteller sollte immer aktualisieren diese header-Variablen nach schreiben ein Stück von Daten, und der Leser sollte niemals Lesen über diese variable. Ein Ding namens "cache-Kohärenz", die alle mainstream-CPU ' s haben die Garantie, dass die Leser sehen, den Speicher schreibt, in der gleichen Reihenfolge Sie geschrieben sind, so dass der Leser nie gelesen uninitialised-Speicher wenn Sie diese Regeln befolgen. (Eine Ausnahme ist, wo der Leser und Autoren auf verschiedenen Server - cache-Kohärenz-funktioniert dort nicht. Versuchen Sie nicht, eine implementieren, shared memory auf verschiedenen Servern!)
Es gibt keine Begrenzung, wie oft Sie aktualisieren die Datei-Ende-Zeiger - alle im Speicher und es werden keine i/o beteiligt, so dass Sie aktualisieren können, es wird jeder Datensatz oder jede Nachricht, die Sie schreiben.
ByteBuffer hat Versionen von "getInt ()" und " putInt ()' - Methoden, die eine absolute byte-Versatz, also das, was ich zum Lesen & schreiben der end-of-file-marker...ich benutze nie die relative Versionen beim arbeiten mit memory-mapped-Dateien.
Es gibt keine Weise, die Sie verwenden sollten, die Größe der Datei oder ein weiteres prozessübergreifende Methode, um die Kommunikation der end-of-file-marker und keine Notwendigkeit oder Vorteil, wenn Sie bereits über shared memory.
Store Reihenfolge wird geachtet auf x86, aber nicht alle anderen "mainstream" - CPU. Auch Ihr compiler/JVM erlaubt werden kann, um die Reihenfolge Ihrer Geschäfte (wenn Sie nicht flüchtig oder bestellt.)
Store Reihenfolge wird geachtet auf allen gängigen Compilern. Es heißt "cache-Kohärenz". Es wurden einige experimentelle CPU ' s entwickelt, die nicht respektieren "cache-Kohärenz", aber Sie wurde nie mainstream. Ich habe diese Technik für viele Jahre auf sehr vielen Computern, Windows und Unix.
Denken Sie daran, wir sind nicht speichern von POJO ' s im shared memory verwenden wir 'getInt()' etc. Methoden, und alles, was hier implizit volatil.
Die Frage wird insbesondere erwähnt, dass "java" und "Interprozesskommunikation". Deshalb reden wir über MappedByteBuffer und getInt(). Warum ist jeder talking about relevanten Fragen nur zu C++ und/oder threads?
InformationsquelleAutor Tim Cooper
Check out my library Mappedbus ( http://github.com/caplogic/mappedbus ), welche ermöglicht mehrere Java-Prozesse (JVMs) zu schreiben, Aufzeichnungen, um die gleiche memory-mapped-Datei.
Hier ist, wie Mappedbus löst das problem Synchronisation zwischen mehreren Autoren:
Den ersten acht bytes der Datei machen, bis ein Feld limit genannt. Dieses Feld gibt an, wie viel Daten tatsächlich in die Datei geschrieben wurden. Die Leser-Umfrage der limit-Feld (mit volatile), um zu sehen, ob es ein neuer Datensatz gelesen werden.
Wenn ein Autor möchte einen Datensatz hinzufügen, um die Datei verwenden die fetch-and-add-Instruktion, atomar aktualisieren Sie die limit-Feld.
Wenn das limit-Feld erhöht hat sich ein Leser wird wissen, dass es neue Daten gelesen werden, aber die Schriftsteller, die aktualisiert das limit-Feld möglicherweise noch nicht geschrieben haben, alle Daten in den Datensatz. Um dieses problem zu vermeiden, jeden Datensatz enthält eine erste byte, die der commit-Bereich.
Wenn ein Autor fertig schreiben eines Datensatzes wird die commit-Feld (mit volatile) und der Leser wird nur mit dem Lesen beginnen einen Datensatz, sobald er gesehen hat, dass die commit-Feld festgelegt wurde.
(BTW, die Lösung wurde nur überprüft, die auf Linux-x86 mit Oracle ' s JVM. Es wird höchstwahrscheinlich nicht auf allen Plattformen funktioniert).
Ja, compareAndSwapLong um genau zu sein. Werfen Sie einen Blick auf die allocate-Methode in MappedBusWriter.java
Zunächst die Lösung mit Verwendung von CAS aber jetzt ist es mit fetch-and-add als Optimierung
InformationsquelleAutor MikaelJ