Como executo um script n vezes ao mesmo tempo e como simulo um semáforo?

3

Eu tenho um arquivo de texto, dentro deste arquivo é um número, e eu tenho um script.sh no ksh. O script lê o arquivo e obtém o número, aumenta o número em 1 e sobregrava o novo número no arquivo, depois dorme por algum tempo e o processo se repete até o número ser igual a 120.

Eu quero que este script seja executado n vezes ao mesmo tempo, como faço isso?

Mas então eu vou ter n processos tentando editar o arquivo.txt e para ter apenas um processo de edição e quando terminar (dorme) o segundo pode editar e assim por diante ...

Provavelmente você vai me dizer que eu tenho que usar um arquivo de trava, mas infelizmente não posso usar isso, então eu tenho que encontrar outra maneira para simular semáforos.

Alguma idéia?

Obrigado.

    
por Jasmin 12.01.2012 / 18:39

3 respostas

2

Eu preferiria controlar a manipulação do número de fora e apenas chamar o script passando o número atual como parâmetro:

n=10
read nr < number.file
seq $((nr+1)) 120 | xargs -n 1 -P $n script.sh

Onde o próprio script seria reduzido para algo como isto:

#!/bin/ksh
number=$1
echo "Do job with/for number $number"
echo $number > number.file
sleep 10

É claro que, se a duração da tarefa puder variar, é melhor verificar o conteúdo atual do arquivo numérico antes de gravá-lo, para não sobrescrever um arquivo maior. Mas isso é importante somente se a sucessão de tarefas precisar dar suporte ao tipo de retomada.

    
por 12.01.2012 / 19:22
1

Fazer isso sem um arquivo de travamento parece requerer algo estranho, porque um arquivo de travamento (melhor ainda, diretório de travamento, já que a criação de diretório deve ser atômica) é a solução padrão e viável para este problema. Eu não fiz exatamente o que você quer fazer, mas aqui estão algumas idéias que me ocorrem:

Você pode escrever um pequeno programa em C para verificar o status de um semáforo SysV. Comece com man semget ou man semop . Isso será tedioso e bizarro.

Você pode usar o Oracle sqlplus e um trecho de PL / SQL que:

lock table table_name in exclusive mode

E, em seguida, outra invocação de sqlplus para liberar o bloqueio. Eu fiz esse tipo de coisa antes, você tem que ter muito cuidado para não deixar processos esperando e liberar o bloqueio. Eu também poderia estar interessado apenas em trabalhar na mesa, então eu só tive uma chamada para sqlplus .

Além disso, no campo à esquerda, você pode usar pipes nomeados como bloqueios de exclusão mútua. Você realmente teria que experimentar isso.

Se você pode carregar um módulo do kernel, talvez o módulo do kernel possa usar um arquivo /proc virtual para atuar como um mutex ou semáforo. O IBM developerWorks tem um artigo em um módulo carregável que cria /proc files.

Talvez você possa implementar o algoritmo de Dekker com valores em arquivos ou canais nomeados.

Depois de revisar o que escrevi, não tenho muita certeza de que qualquer um deles, exceto o programa C fazendo semop() , realmente funcionaria. Todos eles exigem muita experimentação.

    
por 12.01.2012 / 19:36
1

Suposições:

  • Todas as instâncias de script estão sendo executadas na mesma máquina.
  • Existe um diretório conhecido onde seu script pode escrever e que nenhum outro programa usará. Este diretório está em um sistema de arquivos "comum" (em particular, não NFS).

Você pode usar operações atômicas, como criação de arquivos ( set -C; (: >foo) 2>/dev/null ), renomeação ( mv ) e exclusão ( rm ) para lidar com bloqueio.

Para notificar um processo, você pode enviar um sinal; no entanto, é problemático localizar os processos de destino: se você armazenar IDs de processo em algum lugar, não pode ter certeza de que os IDs ainda são válidos, eles podem ter sido reutilizados por um processo não relacionado. Uma maneira de sincronizar dois processos é escrever um byte em um pipe; o leitor irá bloquear até que um escritor apareça e vice-versa.

Primeiro, configure o diretório. Crie um arquivo chamado lock e um pipe nomeado chamado pipe .

if ! [ -d /script-locking-directory ]; then
  # The directory doesn't exist, create and populate it
  {
    mkdir /script-locking-directory-$$ &&
    mkfifo /script-locking-directory-$$/pipe &&
    touch /script-locking-directory-$$/lock &&
    mv /script-locking-directory-$$ /script-locking-directory
  } || {
    # An error happened, so clean up
    err=$?
    rm -r /script-locking-directory-$$
    # Exit, unless another instance of the script created the directory
    # at the same time as us
    if ! [ -d /script-locking-directory ]; then exit $?; fi
  }
fi

Implementaremos o bloqueio renomeando o arquivo lock . Além disso, usaremos um esquema simples para notificar todos os garçons sobre o bloqueio: faça um eco de um byte para um canal e peça a todos os garçons que esperem lendo esse tubo. Aqui está um esquema simples.

take_lock () {
  while ! mv lock lock.held 2>/dev/null; do
    read <pipe # wait for a write on the pipe
  done
}
release_lock () {
  mv lock.held lock
  read <pipe & # make sure there is a reader on the pipe so we don't block
  echo >pipe # notify all readers
}

Este esquema acorda todos os garçons, o que pode ser ineficiente, mas isso não será um problema a menos que haja muita disputa (ou seja, muitos garçons ao mesmo tempo).

O maior problema com o código acima é que, se um porta-trava morre, a trava não será liberada. Como podemos detectar essa situação? Não podemos simplesmente procurar um processo chamado de bloqueio, devido à reutilização do PID. O que podemos fazer é abrir o arquivo de bloqueio no script e verificar se o arquivo de bloqueio está aberto quando uma nova instância de script for iniciada.

break_lock () {
  if ! [ -e "lock.held" ]; then return 1; fi
  if [ -n "$(fuser lock.held)" ]; then return 1; fi
  # If we get this far, the lock holder died
  if mv lock.held lock.breaking.$$ 2>/dev/null; then
    # Check that someone else didn't break the lock and take it just now
    if [ -n "$(fuser lock.breaking.$$)" ]; then
      mv lock.breaking.$$ lock.held
      return 0
    fi
    mv lock.breaking.$$ lock
  fi
  return 0 # whether we did break a lock or not, try taking it again
}
take_lock () {
  while ! mv lock lock.taking.$$ 2>/dev/null; do
    if break_lock; then continue; fi
    read <pipe # wait for a write on the pipe
  done
  exec 9<lock.taking.$$
  mv lock.taking.$$ lock.held
}
release_lock () {
  # lock.held might not exist if someone else is trying to break our lock.
  # So we try in a loop.
  while ! mv lock.held lock.releasing.$$ 2>/dev/null; do :; done
  exec 9<&-
  mv lock.releasing.$$ lock
  read <pipe & # make sure there is a reader on the pipe so we don't block
  echo >pipe # notify all readers
}

Essa implementação ainda pode travar se o detentor de bloqueio morrer enquanto outra instância estiver dentro de take_lock : o bloqueio permanecerá suspenso até que uma terceira instância seja iniciada. Eu também presumo que um script não irá morrer dentro de take_lock ou release_lock .

Aviso: eu escrevi o código acima diretamente em um navegador, eu não testei (e muito menos provou que está correto).

    
por 13.01.2012 / 22:36