2013-01-24 17 views
7

Ho una matrice di nomi di file e ogni processo deve creare e scrivere solo in un singolo file.Crea file in modo thread-safe

Questo è quello che mi è venuto a:

foreach ($filenames as $VMidFile) { 
    if (file_exists($VMidFile)) { // A 
     continue; 
    } 

    $fp = fopen($VMidFile, 'c'); // B 

    if (!flock($fp, LOCK_EX | LOCK_NB)) { // C 
     continue; 
    } 

    if (!filesize($VMidFile)) { // D 
     // write to the file; 

     flock($fp, LOCK_UN); 
     fclose($fp); 
     break; 
    } 

    flock($fp, LOCK_UN); 
    fclose($fp); // E 
} 

Ma non mi piace che sto facendo leva sulla filesize.

Eventuali proposte per farlo in un altro (migliore) modo?

UPD: aggiunto le etichette per discutere facilmente

UPD 2: sto usando filesize perché non vedo altro modo affidabile per verificare se l'attuale filo creato il file (quindi è vuoto ancora)

UPD 3: la soluzione dovrebbe essere condizione corsa libera.

+0

quale problema stai cercando di risolvere facendo questo? – cgTag

+0

@Mathew Foscarini: gestisce le risorse di terze parti che non dispongono della sincronizzazione della concorrenza. – zerkms

risposta

3

Una possibile, un po' brutta soluzione sarebbe quella di bloccare in un file di blocco e poi testare se il file esiste:

$lock = fopen("/tmp/".$filename."LOCK", "w"); // A 

if (!flock($lock, LOCK_EX)) { // B 
    continue; 
} 
if(!file_exists($filename)){ // C 
    //File doesn't exist so we know that this thread will create it 
    //Do stuff to $filename 
    flock($lock, LOCK_UN); // D 
    fclose($lock); 
}else{ 
    //File exists. This thread didn't create it (at least in this iteration). 
    flock($lock, LOCK_UN); 
    fclose($lock); 
} 

Ciò dovrebbe consentire l'accesso esclusivo al file e inoltre consente di decidere se la chiamata a fopen($VMidFile, 'c'); creerà il file.

+0

Sì, questo potrebbe funzionare (file di sincronizzazione per i file di sincronizzazione - "dobbiamo andare più in profondità") ;-) +1 – zerkms

+0

ma non può perdere un blocco se si blocca nella sezione critica oO – Dmitry

2

Piuttosto che creare un file e sperando che non è interferito con:

  1. creare un file temporanea
  2. fare tutte le operazioni necessarie di file su di esso
  3. rename alla nuova posizione se la posizione non esiste

Tecnicamente, poiché rename sovrascrive la destinazione, esiste la possibilità che i thread concorrenti continuino a scontrarsi. Questo è molto improbabile se si dispone di:

if(!file_exists($lcoation) { rename(... 

si potrebbe usare md5_file per verificare il contenuto del file è corretto dopo questo blocco.

+0

"rinominalo nella nuova posizione se la posizione non esiste." --- come lo controlleresti in modo sicuro? "c'è una possibilità" - non voglio affidarmi al "caso". "Questo è molto improbabile" --- Non voglio fare affidamento su "probabile o no", voglio la soluzione che ** garantisce ** che funzionerà sempre come previsto. – zerkms

+0

Quindi hai una proposta libera da condizioni? Ora non è una risposta, mi dispiace. – zerkms

+0

Mi piace questa proposta; rende minuscola la sezione critica. – Dmitry

1

si può garantire l'accesso esclusivo tramite semaphores (solo UNIX, e ha fornito l'estensione sysvsem è installato):

$s = sem_get(ftok($filename), 'foo'); 
sem_acquire($s); 

// Do some critical work... 

sem_release($s); 

In caso contrario è anche possibile utilizzare flock. Non richiede alcuna estensione speciali, ma secondo comments on PHP.net è un po 'più lento di quello che usano i semafori:

$a = fopen($file, 'w'); 
flock($a, LOCK_EX); 

// Critical stuff, again 

flock($a, LOCK_UN); 
+0

Se controlli la mia domanda vedrai che sto già usando 'flock' – zerkms

+0

Ouch, hai ragione ... :) Allora, che ne dici di usare i semafori? – helmbert

+0

porteranno alla stessa domanda: come verificare se il file è stato creato dal processo corrente. Non sono sicuro di aver capito come posso sincronizzare 'file_exists' con il semaforo – zerkms

0

Utilizzare la modalità 'x' anziché 'c' nella chiamata fopen. E controlla il $ fp risultante, se è falso, il file non è stato creato dal thread corrente, e dovresti continuare con il prossimo nome file.

Inoltre, a seconda delle impostazioni di installazione del PHP, è possibile mettere un @ davanti alla chiamata fopen per sopprimere eventuali avvisi se fopen ($ VMidFile, 'x') non è in grado di creare il file perché già esistente.

Questo dovrebbe funzionare anche senza gregge.

+0

Cosa succede se uno script muore e non ripulisce il file? Richiederebbe un'interazione umana per pulire nuovamente il file, non è vero? – zerkms

+0

Non sai come si riferisce al problema originale, cosa intendi con 'ripulire il file'? Cancellalo in seguito, o ..? Può essere gestito automaticamente, purché sia ​​possibile definire le condizioni esatte che determinano se un altro thread potrebbe ancora essere in esecuzione e funzionare sul file, o se si è arrestato in modo anomalo e il file è diventato orfano. – Rogier

+0

quindi questa è la domanda originale: "finché è possibile definire le condizioni esatte che determinano se un altro thread può ancora essere in esecuzione" --- questo è quello che ho chiesto per un meccanismo di blocco. E il meccanismo dovrebbe essere affidabile in modo che non richiedesse ulteriori euristiche. Con la tua proposta vedo che l'algoritmo si bloccherà se un processo muore e non rimuove il file. – zerkms