¿Qué comandos de Unix se pueden usar como semáforo / bloqueo?


34

Quiero ejecutar múltiples scripts de shell Bash en paralelo. Sin embargo, quiero evitar las condiciones de carrera. ¿Qué comandos de Unix son realmente atómicos que podría usar para este propósito y cómo puedo usarlos?


¿Qué haces que requiere trabajo paralelo? ¿No puedes expresar las dependencias de una manera que permita que un paralelo se make(1)haga cargo? (es decir, haz un make -j 9si tienes 8 núcleos)? Esto tiene la ventaja adicional de entrelazar el trabajo con una granularidad más fina.
vonbrand

Respuestas:


30

Si lockfileno está instalado en su sistema, entonces mkdirhará el trabajo: es una operación atómica y falla si el directorio ya existe (siempre y cuando no agregue el -pinterruptor de línea de comandos).

create_lock_or_wait () {
  path="$1"
  wait_time="${2:-10}"
  while true; do
        if mkdir "${path}.lock.d"; then
           break;
        fi
        sleep $wait_time
  done
}

remove_lock () {
  path="$1"
  rmdir "${path}.lock.d"
}

26

flock(1)

#!/bin/bash

# Makes sure we exit if flock fails.
set -e

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

Esto garantiza que el código entre "(" y ")" se ejecute solo por un proceso a la vez y que el proceso espere un bloqueo demasiado tiempo.


Buena, no lo sabía. Sin embargo, aparentemente es específico de Linux ...
Riccardo Murri

1
@Riccardo, FreeBSD tiene un comando similar: lockf(1).
Alex B

lockf(1)Sin embargo, no funciona de la manera utilizada en este ejemplo. No puede tomar un número de descriptor de archivo como argumento.
Charley

11

lockfile (1) parece un buen candidato, aunque tenga en cuenta que es parte del paquete procmail , que es posible que aún no haya instalado en su máquina. Es un paquete suficientemente popular que debería empaquetarse para su sistema si aún no está instalado. Tres de los cuatro sistemas que verifiqué lo tienen, y el otro lo tiene disponible.

Usarlo es simple:

#!/bin/sh
LOCKFILE=$HOME/.myscript/lock
mkdir -p `dirname $LOCKFILE`

echo Waiting for lock $LOCKFILE...
if lockfile -1 -r15 $LOCKFILE
then
    # Do protected stuff here
    echo Doing protected stuff...

    # Then, afterward, clean up so another instance of this script can run
    rm -f $LOCKFILE
else
    echo "Failed to acquire lock!  lockfile(1) returned $?"
    exit 1
fi

Las opciones que he dado hacen que vuelva a intentarlo una vez por segundo durante hasta 15 segundos. Suelta la bandera "-r" si quieres que espere para siempre.


2
Solo como referencia: la página de manual : linux.die.net/man/1/lockfile . :)
Lucas Jones

Tenga en cuenta que (de acuerdo con la página de manual), "Una vez que un archivo está bloqueado, el bloqueo debe tocarse al menos una vez cada cinco minutos o el bloqueo se considerará obsoleto y los intentos de bloqueo posteriores tendrán éxito".
Jay

6

La llamada al sistema mkdir()es atómica en los sistemas de archivos POSIX. Entonces, usar el mkdircomando de tal manera que implique exactamente una llamada para mkdir()lograr su propósito. (IOW, no lo uses mkdir -p). El desbloqueo correspondiente esrmdir por supuesto.

Advertencia: es mkdir()posible que no sea atómico en los sistemas de archivos de red.


¿es rmdirpor lo tanto también atómico?
Alexej Magura

3

Tal vez el comando lockfile hará lo que necesita.

lockfile ~/.config/mylockfile.lock
.....
rm -f important.lock

Esto parece eliminar el archivo incorrecto.
Benjamin W.

1

Si solo está ejecutando en Unix, use fifos. Puede escribir registros de trabajo en el Fifo y hacer que los procesos se lean desde este archivo y sus lectores bloquearán en el Fifos.

Los archivos de bloqueo están bien, pero por lo que describe iría con fifos


1
¿Podrías explicar cómo crees que los quintos pueden prevenir las condiciones de carrera?
G-Man dice 'Reincorporar a Monica' el

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.