2012-04-02 17 views
13

Tengo un programa de Java que usa 20 hilos. Cada uno de ellos escribe sus resultados en un archivo llamado output.txt.Hilos y escritura de archivos

Siempre obtengo un número diferente de líneas en output.txt.

¿Puede haber un problema con la sincronización de los hilos? ¿Hay alguna manera de manejar esto?

+0

Bueno, esto no está muy claro cómo es su implementación. Como muestra mi caso de prueba simple, obtengo líneas constantes de salida con FileWriter con 20 hilos. Puede ser necesario agregar algunos detalles de implementación. Ver mi respuesta – FaithReaper

Respuesta

26

¿Puede ser un problema de sincronización de hilos?

Sí.

¿Hay una manera de manejar esto?

Sí, asegúrese de que las escrituras se serialicen sincronizando en un mutex relevante. O, alternativamente, solo tiene un hilo que realmente da salida al archivo, y tiene todos los otros hilos simplemente cola de texto para escribir en una cola de la que se extrae el hilo de escritura. (De esta manera los 20 temas principales no bloquean en la I/O.)

el mutex Re: Por ejemplo, si que están utilizando el mismo FileWriter instancia (o lo que sea), lo que me referiré como fw, entonces podrían utilizarlo como un mutex:

synchronized (fw) { 
    fw.write(...); 
} 

Si están utilizando cada uno su propio FileWriter o lo que sea, encontrar algo más que todos ellos comparten a ser el mutex.

Pero, una vez más, tener un hilo haciendo la E/S en nombre de los demás es también una buena forma de hacerlo.

+0

¿Podría ser más específico sobre cuál sería el mejor enfoque para el enfoque de un solo escritor? P.ej. tal vez usando un [Executor de subproceso único] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor--) y simplemente enviando las tareas a ese 'Ejecutor ¿? – Roland

9

Le sugiero que lo organice de esta manera: Un consumidor de hilos consumirá todos los datos y los escribirá en el archivo. Todos los subprocesos de trabajo producirán datos en el subproceso de consumidor de forma síncrona. O con la escritura de archivos de varios hilos, puede usar algunas implementaciones de mutex o bloqueos.

+1

+1 Estaba agregando esta sugerencia a mi respuesta mientras escribía la suya. :-) –

1

Debe utilizar la sincronización en este caso. Imagine que 2 hilos (t1 y t2) abren el archivo al mismo tiempo y comienzan a escribir en él. Los cambios realizados por el primer subproceso se sobrescriben en el segundo subproceso porque el segundo subproceso es el último en guardar los cambios en el archivo. Cuando un hilo t1 está escribiendo en el archivo, t2 debe esperar hasta que t1 termine su tarea antes de que pueda abrirlo.

2

Si desea cualquier apariencia de rendimiento y facilidad de administración, vaya con la cola productor-consumidor y solo un escritor de archivos, como lo sugirieron Alex y otros. Dejar todos los hilos en el archivo con un mutex es simplemente desordenado: cada retraso en el disco se transfiere directamente a la funcionalidad principal de la aplicación (con una mayor contención). Esto es especialmente poco divertido con las unidades de red lentas que tienden a desaparecer sin previo aviso.

1

Si usted puede mantener su archivo como un FileOutputStream se puede bloquear de esta manera:

FileOutputStream file = ... 
.... 
// Thread safe version. 
void write(byte[] bytes) { 
    try { 
    boolean written = false; 
    do { 
     try { 
     // Lock it! 
     FileLock lock = file.getChannel().lock(); 
     try { 
      // Write the bytes. 
      file.write(bytes); 
      written = true; 
     } finally { 
      // Release the lock. 
      lock.release(); 
     } 
     } catch (OverlappingFileLockException ofle) { 
     try { 
      // Wait a bit 
      Thread.sleep(0); 
     } catch (InterruptedException ex) { 
      throw new InterruptedIOException ("Interrupted waiting for a file lock."); 
     } 
     } 
    } while (!written); 
    } catch (IOException ex) { 
    log.warn("Failed to lock " + fileName, ex); 
    } 
} 
+0

Esto es totalmente redundante dado que 'synchronized' existe. – EJP

+0

@EJP - ver [FileLock] (https://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html) - * los bloqueos que se mantienen en un archivo deben ser visibles para todos programas que tienen acceso al archivo, independientemente del idioma en el que se escriben esos programas *; por lo tanto, en teoría, * deberían * ser mejor que 'sincronizados ', pero a menudo no lo son. – OldCurmudgeon

+0

Eso no lo convierte en no redundante, o 'mejor que 'sincronizado' tampoco. No hay nada sobre otros procesos u otros idiomas en la pregunta. – EJP

0

Bueno, sin ningún detalle de implementación, es difícil saber, pero como mi caso de prueba muestra, siempre me 220 líneas de salida, es decir, número constante de líneas, con FileWriter. Observe que no se usa synchronized aquí.

import java.io.File; 
import java.io.FileWriter; 
import java.io.IOException; 
/** 
* Working example of synchonous, competitive writing to the same file. 
* @author WesternGun 
* 
*/ 
public class ThreadCompete implements Runnable { 
    private FileWriter writer; 
    private int status; 
    private int counter; 
    private boolean stop; 
    private String name; 


    public ThreadCompete(String name) { 
     this.name = name; 
     status = 0; 
     stop = false; 
     // just open the file without appending, to clear content 
     try { 
      writer = new FileWriter(new File("test.txt"), true); 
     } catch (IOException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

    } 


    public static void main(String[] args) { 

     for (int i=0; i<20; i++) { 
      new Thread(new ThreadCompete("Thread" + i)).start(); 
     } 
    } 

    private int generateRandom(int range) { 
     return (int) (Math.random() * range); 
    } 

    @Override 
    public void run() { 
     while (!stop) { 
      try { 
       writer = new FileWriter(new File("test.txt"), true); 
       if (status == 0) { 
        writer.write(this.name + ": Begin: " + counter); 
        writer.write(System.lineSeparator()); 
        status ++; 
       } else if (status == 1) { 
        writer.write(this.name + ": Now we have " + counter + " books!"); 
        writer.write(System.lineSeparator()); 
        counter++; 
        if (counter > 8) { 
         status = 2; 
        } 

       } else if (status == 2) { 
        writer.write(this.name + ": End. " + counter); 
        writer.write(System.lineSeparator()); 
        stop = true; 
       } 
       writer.flush(); 
       writer.close(); 
      } catch (IOException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

como lo entiendo (y prueba), hay dos fases en este proceso:

  • todos los hilos en la piscina todo crea y se inicia, listo para agarrar el archivo;
  • uno de ellos lo agarra, y supongo que internamente lo bloquea, impide que otros subprocesos tengan acceso, porque nunca veo una línea combinada de contenidos que provienen de dos hilos. Entonces, cuando un hilo está escribiendo, otros están esperando hasta que complete la línea, y muy probablemente, libere el archivo. Entonces, no habrá condiciones de carrera.
  • el más rápido de los demás toma el archivo y comienza a escribir.

Bueno, es igual que una multitud que esperaba fuera de un cuarto de baño, sin hacer cola .....

tanto, si su aplicación es diferente, muestran el código y nos puede ayudar a romper hacia abajo.

Cuestiones relacionadas