Existe alguma maneira de fazer fila de tarefas de longa duração?

2

Existe uma maneira de fazer o seguinte em um terminal unix:

  1. Iniciar um processo de longa duração
  2. Adicione outro processo de longa execução para começar quando o anterior for concluído
  3. Repita a etapa 2 até que eu tenha na fila os processos que preciso executar

O motivo pelo qual peço é que eu tenha algumas coisas de longa duração que preciso fazer. Eu poderia colocar todos os comandos em um script bash simples e apenas executar isso, mas o problema é que nem sempre tenho certeza exatamente o que preciso executar. Então, se eu começar o script e depois lembrar de outro que eu deveria executar, eu preciso acertar ^ C várias vezes até que todos os processos sejam eliminados, edite o script e adicione meu novo processo e então reinicie tudo.

O que eu especificamente estou fazendo agora é copiar muitos arquivos grandes para vários discos rígidos externos, e já que não sei exatamente quais eu preciso copiar e para onde agora gostaria de começar os que eu sei que preciso copiar e, em seguida, adicionar à fila enquanto descubro o resto.

Espero que tenha sentido ... É possível algo assim?

    
por Svish 10.12.2010 / 00:19

5 respostas

3

A casca é perfeita para isso.

  1. Inicie o primeiro programa.
  2. Pressione Ctrl-z para suspender o programa.
  3. Inicie o próximo programa.
  4. Repita as etapas 2 e 3 para adicionar mais programas à fila. Todos os programas estarão no modo suspenso.
  5. Agora, execute a fila assim:

    while fg; do :; done
    

    Você não pode suspender este loop while nem sair do bash até que a fila esteja pronta.

Se você precisar fazer logout (por exemplo, os programas serão executados por vários dias), considere executar as etapas acima em screen .

    
por 10.12.2010 / 03:10
1

Não é muito elegante, mas rápido e sujo:

process1 &
while kill -0 $! ; do sleep 1; done && process2 &
while kill -0 $! ; do sleep 1; done && process3 &

Você precisaria substituir os PIDs reais por $! se tiver executado trabalhos em segundo plano intervenientes.

    
por 10.12.2010 / 02:46
1

A resposta para sua pergunta geral é: Absolutamente. Nos primórdios da computação, o processamento em lote era a única maneira de fazer qualquer coisa, e mesmo quando sistemas interativos multiusuários eram inventados, o recurso de processamento em lote era a norma para tarefas grandes. E ainda é comumente feito hoje em ambientes de médio e grande porte usando sistemas como Sun Grid Engine ou Torque .

No entanto, isso é provavelmente um exagero para o que você precisa. Você poderia configurar um sistema mais leve para executar scripts em uma fila serial, mas não acho que essa abordagem seja particularmente adequada para sua tarefa específica. Presumir cópias paralelas para unidades diferentes é aceitável, acho que eu iria atacá-lo assim:

  1. Crie uma estrutura de diretório correspondente às suas unidades de destino:

    ~/copysystem/drive1/ %código% ~/copysystem/drive2/

  2. Instale o Incron .

  3. Configure uma entrada incrontab para cada um desses diretórios, que executa seu script de cópia automaticamente em IN_MOVED_TO.

  4. Faça seu script a) matar quaisquer instâncias anteriores do mesmo script quando ele for iniciado ou b) use um arquivo de bloqueio baseado em ~/copysystem/drive3/ e bloqueie até que o bloqueio seja limpo. / p>

Então, tudo o que você precisa fazer é mover os arquivos para os vários diretórios mkdir e todos eles são copiados magicamente para o seu destino.

Especialmente no caso de 4a, você provavelmente quer usar ~/copysystem/drive# para copiar seus arquivos, para que você possa reiniciar as transferências parciais do meio. (Possivelmente em combinação com rsync -aP , se você quiser se livrar dos originais.)

Se você quiser pular a complicação de usar o incron, você ainda pode tirar proveito de fazer o seu bloco de scripts em um arquivo de bloqueio. Isso funciona mais ou menos assim:

#!/bin/bash
LOCKDIR="/var/run/copysystem/copysystem.lock"

while ! mkdir $LOCKDIR ; do
  echo "waiting for lock"
  sleep 5
done
trap rmdir $LOCKDIR EXIT

rsync commands go here....

Isso funciona porque --remove-sent-files é uma operação atômica - se tiver sucesso, você sabe que o diretório não existia. Isso é importante, porque se você usar algo como mkdir , há uma condição de corrida. (Mesmo com a verificação da tabela de processos para comandos rsync ou algo semelhante.)

    
por 10.12.2010 / 02:56
0

Se isso é algo que você fará regularmente, vale a pena configurar um agendador para gerenciá-lo para você.

Eu gosto da elegância da solução de Aleksandr - e ela aborda todos os pontos que você criou originalmente - mas eu posso ver que ela tem limitações.

Eu já usei o BSD lpd como um método para enfileirar trabalhos - o 'driver de impressora' é apenas um shell script, então é fácil se adaptar a diferentes tarefas (no meu caso isso significa gerenciar 4 modems para polling de dados, envio faxes, SMS e outras coisas).

    
por 10.12.2010 / 11:52
0

O que você quer é uma fila de comandos não interativa. Boas notícias! Eu escrevi um para você.

enqueue_cmd :

#!/usr/bin/perl -w

# Enqueue a job

use strict;

use Fcntl qw(:flock);

my $JOB_QUEUE_FILE = '/var/tmp/job_queue';
my $LOCK_TRIES = 5;
my $LOCK_SLEEP = 1;

my $jq;
open $jq, ">> $JOB_QUEUE_FILE" or die "!open $JOB_QUEUE_FILE: $!";

my $locked = 0;

LOCK_ATTEMPT: for (my $lock_tries = 0; $lock_tries < $LOCK_TRIES;
    $lock_tries++)
{
    if (flock $jq, LOCK_EX) {
            $locked = 1;
            last LOCK_ATTEMPT;
    }

    sleep $LOCK_SLEEP;
}

$locked or die "could not lock $JOB_QUEUE_FILE";

for (@ARGV) {
    print $jq "$_\n";
}

close $jq;

dequeue_cmd :

#!/usr/bin/perl -w

# Dequeue a jobs and run them

use strict;

use Fcntl qw(:seek :flock);
use FileHandle;

my $QUEUE_FILE = '/var/tmp/job_queue';
my $OUTPUT_FILE = '/var/tmp/job_out';
my $LOCK_TRIES = 5;
my $LOCK_SLEEP = 1;
my $JOB_SLEEP = 1;

my $locked;

my $jo;
open $jo, ">> $OUTPUT_FILE" or die "!open $OUTPUT_FILE: $!";
$jo->autoflush(1);

my $jq;
open $jq, "+< $QUEUE_FILE" or die "!open $QUEUE_FILE: $!";

my @jobs = ( );
my $job;

JOB: while (1) {

    if (-s $QUEUE_FILE == 0) {
            sleep $JOB_SLEEP;
            next JOB;
    }

    $locked = 0    

    LOCK_ATTEMPT: for (my $lock_tries = 0; $lock_tries < $LOCK_TRIES;
            $lock_tries++)
    {
            if (flock $jq, LOCK_EX) {
                    $locked = 1;
                    last LOCK_ATTEMPT;
            }

            sleep $LOCK_SLEEP;
    }
    $locked or die "could not lock $QUEUE_FILE";

    seek $jq, 0, SEEK_SET or die "could not seek to start of file";

    push @jobs, <$jq>;

    truncate $jq, 0 or die "could not truncate $QUEUE_FILE";
    seek $jq, 0, SEEK_SET or die "could not seek to start of $QUEUE_FILE";

    flock $jq, LOCK_UN;

    for $job (@jobs) {
            chomp $job;
            print $jo "## executing $job\n";
            print $jo '$job';
    }

    sleep $JOB_SLEEP;
}

Primeiro, execute nohup ./dequeue_cmd & , depois adicione seus comandos da seguinte forma:

./enqueue_cmd "echo 'hello world'" "sleep 5" "date"
./enqueue_cmd "ls /var/tmp"

A saída aparece em /var/tmp/job_out :

tail -F /var/tmp/job_out
## executing echo 'hello world'
hello world
## executing sleep 5
## executing date
Fri Dec 10 16:35:43 PST 2010
## executing ls /var/tmp
ff
job_out
job_queue
ss
    
por 11.12.2010 / 01:37