Recentemente un mio collega doveva eseguire più processi paralleli su un server (circa 20 nel nostro caso specifico), ogni processo faceva delle “cose”, e questo può richiedere un tempo più lungo o più corto rispetto agli altri, una volta che un processo termina il suo compito deve leggere da un file di testo la prima riga che indica l’ID di un nuovo lavoro, rimuove tale ID dalla parte superiore del file di testo e inizia ad elaborarlo.
Problema: su un lungo periodo (una elaborazione con più di 50K ID) capita “spesso” che 2 processi terminano nello stesso tempo e così entrambi hanno lo stesso ID causando problemi a tutto il processo, si è cercato di utilizzare un file di lock semplice facendo un “touch” quando il processo apriva il file con l’elenco di ID, ma sembra che questa operazione è troppo lento e abbiamo avuto ancora qualche caso di concorrenza fallito.
La soluzione è stata utilizzare la funzione di bash flock
flock(2) è usato per mettere dei meccanismi di lock ai file aperti. può essere utilizzato per sincronizzare l’accesso a risorse tra più processi in esecuzione. Mentre flock(2) non agisce esclusivamente su file (in realtà, gli handle dei file), il file stesso non deve essere la risorsa il cui accesso è controllato. Invece, il file può essere utilizzato come un semaforo per controllare l’accesso a una sezione critica, in cui ogni risorsa può accedere senza preoccupazioni concorrenza.
Suona complicato ?
Forse un piccolo esempio aiuterà, questo è il mio script /tmp/hello.sh:
#!/bin/bash set -e scriptname=$(basename $0) lock="/var/run/${scriptname}" exec 200>$lock flock -n 200 || exit 1 ## The code: pid=$$ echo $pid 1>&200 sleep 60 echo "Hello world" |
Qualche spiegazione delle parti interessanti del codice:
set -e |
Quando questa opzione è attiva, se un semplice comando non riesce per una ragione o restituisce un valore di stato di uscita> 0, e non fa parte della lista composta dalle parole chiave while, until, o if e non è una parte di una lista AND o OR, e non è una pipe preceduta dalla parola riservata !, allora la shell deve uscire immediatamente.
exec 200>$lock |
Normally “exec” in a shell script is used to turn over control of the script to some other program. But it can also be used to open a file and name a file handle for it. Normally, every script has standard input (file handle 0), standard output (file handle 1) and standard error (file handle 2) opened for it. The call “exec 200>$lock” will open the file named in $lock for reading, and assign it file handle 200
Normalmente “exec” in uno script di shell è utilizzato per trasferire il controllo dello script da un altro programma. Ma può anche essere utilizzato per aprire un file e denominare un handle di file per esso. Normalmente, ogni script ha uno standard input (descrittore di file 0), lo standard output (descrittore di file 1) e lo standard error (descrittore di file 2) aperti per lui. La chiamata “exec 200> $lock” aprirà il file contenuto in $lock per la lettura, e lo assegnerà all’handle di file 200
flock -n 200||exit 1 |
Dice a flock di bloccare esclusivamente il file indicato dall’handle di file 200 o uscire con codice 1. Lo stato di essere bloccato dura dopo la chiamata di flock, perché l’handle di file è ancora valido. Questo stato durerà fino a quando l’handle di file sarà chiuso, in genere quando lo script esce.
Dopo di che raccolgo nella variabile $pid il PID di questo processo e lo scrivo nel file di lock, aspetto 60 secondi (per verificare che cosa succede se lo script viene eseguito una seconda volta) e alla fine ho dato il mio importante messaggio al mondo.
E questo è l’output (in modalità detatgliata) dello script:
Prima esecuzione:
bash -x /tmp/hello.sh + set -e ++ basename /tmp/hello.sh + scriptname=hello.sh + lock=/var/run/hello.sh + exec + flock -n 200 + pid=4683 + echo 4683 + sleep 60 + echo 'Hello world' Hello world |
Seconda esecuzione (mentre la prima è in sleep.):
bash -x /tmp/hello.sh + set -e ++ basename /tmp/hello.sh + scriptname=hello.sh + lock=/var/run/hello.sh + exec + flock -n 200 + exit 1 |
Le principali opzioni che è possibile utilizzare con il gregge di comando sono:
-s, –shared
Ottiene un blocco condiviso, a volte chiamato un blocco di lettura.
-x, -e, –exclusive
Ottiene un blocco esclusivo, a volte chiamato un blocco in scrittura. Questa è l’impostazione predefinita.
-u, –unlock
Toglie un lucchetto. Questo non è di solito necessario, dal momento che un blocco viene eliminato automaticamente quando il file viene chiuso. Tuttavia, può essere richiesto in casi particolari, per esempio, se il gruppo di comando racchiuso può aver un fork del processo in background che non deve mantenere il blocco.
-n, –nb, –nonblock
Fail (con un codice di uscita 1), piuttosto che attendere se il blocco non può essere acquisito immediatamente.
-w, –wait, –timeout seconds
Fail (con un codice di uscita 1), se il blocco non può essere acquisito nei secondi indicati. Sono ammessi valori frazionari decimali.
Conclusioni
Flock è una funzione semplice che può assicurare di eseguire più job/processi senza alcun problema di risorse che devono essere lette o scritte da un solo processo alla volta.
Popular Posts:
- None Found
I discovered flock recently and use it every time the system boots and runs many iptables rules from the script. iptables looks like doesn’t have a lock function, so the consequence is some iptables rules are not loaded at all.
So I always instead of:
IPT=`which iptables`
use
IPT=’flock /dev/shm/iptables.lock iptables’
You could look at this free software project as well: http://sourceforge.net/projects/flom/
It must me compiled and installed, but after that step, everything becomes as easy as running
flom — my_command_I_need_to_synchronize
Cheers
Ch.F.
What if we use
exec 200<$0
flock -n 200 || exit 1
to lock the current script.
Nice article!
Thanks!
– Vj
| The call “exec 200>$lock” will open the file named in $lock for reading, and assign it file handle 200
I think “reading” should be “writing”, as referenced here https://www.gnu.org/software/bash/manual/html_node/Redirections.html