java multithreading randomaccessfile filechannel

Concurrencia de RandomAccessFile en Java



multithreading filechannel (2)

Como indica la documentación y Adonis ya lo menciona, una escritura solo puede ser realizada por un hilo a la vez. Además, no obtendrá ganancias de rendimiento a través de la concurrencia; además, solo debe preocuparse por el rendimiento si se trata de un problema real, ya que la escritura simultánea en un disco puede degradar su rendimiento (probablemente menos para las unidades de estado sólido que las unidades de disco duro).

El medio subyacente es en la mayoría de los casos (SSD, HDD, Red) de un solo subproceso: en realidad, no existe un subproceso en el nivel de hardware, los subprocesos no son más que una abstracción.

En su caso el medio es un SSD. Si bien el SSD internamente puede escribir datos en varios módulos al mismo tiempo (pueden alcanzar un nivel de paralelismo en el que las escrituras pueden ser tan rápidas e incluso superar la lectura), las estructuras de datos de mapeo internas son recursos compartidos y, por lo tanto, se sostienen, especialmente en actualizaciones frecuentes, como escribe Sin embargo, las actualizaciones de esta estructura de datos son bastante rápidas y, por lo tanto, no hay que preocuparse a menos que se convierta en un problema.

Pero aparte de esto, esos son solo los aspectos internos del SSD. En el exterior, se comunica a través de una interfaz Serial ATA, por lo tanto, un byte a la vez (en realidad paquetes en una estructura de información de trama, FIS). Además de esto, hay un sistema operativo / sistema de archivos que, una vez más, tiene una estructura de datos probablemente en conflicto y / o aplica sus propios medios de optimización, como el almacenamiento en caché detrás de la escritura.

Además, como sabe cuál es su medio, puede optimizar especialmente para eso y las unidades SSD son realmente rápidas cuando un solo hilo escribe una gran cantidad de datos.

Por lo tanto, en lugar de utilizar varios subprocesos para escribir, puede crear un búfer en memoria grande (probablemente considere un archivo asignado en memoria) y escribir simultáneamente en este búfer. La memoria en sí no es contendida, siempre que se asegure de que cada hilo acceda a su propio espacio de direcciones del búfer. Una vez que se completan todos los subprocesos, escribe este único búfer en el SSD (no es necesario si se usa un archivo asignado en memoria).

Vea también este buen resumen sobre el desarrollo de SSD: Un resumen: lo que todo programador debe saber sobre las unidades de estado sólido

El punto para realizar una preasignación (o para ser más precisos, file_.setLength() , que se asigna de manera ftruncate a ftruncate ) es que el cambio de tamaño del archivo puede usar ciclos adicionales y es posible que no pueda evitarlo. Pero, de nuevo, esto puede depender del sistema operativo / sistema de archivos.

Estoy creando un objeto RandomAccessFile para escribir en un archivo (en SSD) por varios subprocesos. Cada hilo intenta escribir un búfer de bytes directo en una posición específica dentro del archivo y me aseguro de que la posición en la que se escribe un hilo no se superponga con otra cadena:

file_.getChannel().write(buffer, position);

donde file_ es una instancia de RandomAccessFile y el buffer es un búfer de byte directo.

Para el objeto RandomAccessFile, ya que no estoy usando fallocate para asignar el archivo, y la longitud del archivo está cambiando, ¿utilizará esto la concurrencia de los medios subyacentes?

Si no es así, ¿hay algún punto en el uso de la función anterior sin llamar a fallocate al crear el archivo?


Hice algunas pruebas con el siguiente código:

public class App { public static CountDownLatch latch; public static void main(String[] args) throws InterruptedException, IOException { File f = new File("test.txt"); RandomAccessFile file = new RandomAccessFile("test.txt", "rw"); latch = new CountDownLatch(5); for (int i = 0; i < 5; i++) { Thread t = new Thread(new WritingThread(i, (long) i * 10, file.getChannel())); t.start(); } latch.await(); file.close(); InputStream fileR = new FileInputStream("test.txt"); byte[] bytes = IOUtils.toByteArray(fileR); for (int i = 0; i < bytes.length; i++) { System.out.println(bytes[i]); } } public static class WritingThread implements Runnable { private long startPosition = 0; private FileChannel channel; private int id; public WritingThread(int id, long startPosition, FileChannel channel) { super(); this.startPosition = startPosition; this.channel = channel; this.id = id; } private ByteBuffer generateStaticBytes() { ByteBuffer buf = ByteBuffer.allocate(10); byte[] b = new byte[10]; for (int i = 0; i < 10; i++) { b[i] = (byte) (this.id * 10 + i); } buf.put(b); buf.flip(); return buf; } @Override public void run() { Random r = new Random(); while (r.nextInt(100) != 50) { try { System.out.println("Thread " + id + " is Writing"); this.channel.write(this.generateStaticBytes(), this.startPosition); this.startPosition += 10; } catch (IOException e) { e.printStackTrace(); } } latch.countDown(); } } }

Hasta aquí lo que he visto:

  • Windows 7 (partición NTFS): se ejecuta de forma lineal (alias un hilo se escribe y cuando termina, otro se ejecuta)

  • Linux Parrot 4.8.15 (partición ext4) (distribución basada en Debian), con Linux Kernel 4.8.0: los hilos se entremezclan durante la ejecución

Nuevamente como dice la documentation :

Los canales de archivos son seguros para ser utilizados por múltiples hilos concurrentes. El método de cierre se puede invocar en cualquier momento, según lo especificado por la interfaz del canal. Solo una operación que involucre la posición del canal o puede cambiar el tamaño de su archivo puede estar en progreso en un momento dado; los intentos de iniciar una segunda operación de este tipo mientras la primera todavía está en curso se bloquearán hasta que se complete la primera operación. Otras operaciones, en particular aquellas que toman una posición explícita, pueden proceder concurrentemente; si, de hecho, lo hacen depende de la implementación subyacente y, por lo tanto, no está especificado.

Por lo tanto, sugiero que primero lo intente y vea si los sistemas operativos en los que va a implementar su código (posiblemente el tipo de sistema de archivos) admiten la ejecución paralela de una llamada FileChannel.write

Edición : Como se señaló, lo anterior no significa que los subprocesos puedan escribir simultáneamente en el archivo, en realidad es lo contrario, ya que la llamada de write comporta de acuerdo con el contrato de un WritableByteChannel que especifica claramente que solo un subproceso puede escribir a un archivo dado:

Si un hilo inicia una operación de escritura en un canal, cualquier otro hilo que intente iniciar otra operación de escritura se bloqueará hasta que se complete la primera operación.