Header Ads

Como criar um semáforo no Bash

Você está desenvolvendo um aplicativo multithread? Mais cedo ou mais tarde, você provavelmente precisará usar um semáforo. Neste artigo, você aprenderá o que é um semáforo, como criar / implementar um no Bash e muito mais.

O que é um semáforo?

Um semáforo é uma construção de programação que é usada em programas de computador que empregam vários threads de processamento (threads de processamento de computador em que cada um executa o código-fonte do mesmo programa ou conjunto de programas) para obter o uso exclusivo de um recurso comum em um determinado ponto no tempo. Dito de uma maneira muito mais fácil, pense nisso como “ um de cada vez, por favor. ”

Um semáforo foi definido pela primeira vez no final da década de 1960 pelo falecido cientista da computação Edsger Dijkstra de Rotterdam, na Holanda. Você provavelmente já usou semáforos muitas vezes em sua vida sem perceber especificamente que estava fazendo isso!

Ficando na Holanda por um tempo, um país cheio de pequenos cursos de água e muitas pontes móveis (chamada de ponte levadiça em inglês americano), pode-se ver muitos exemplos excelentes da vida real de um semáforo; considere a alça de um operador de ponte levadiça: para cima ou para baixo. Esse identificador é a variável de semáforo que protege o curso de água ou a estrada de acidentes. Assim, a hidrovia e as estradas podem ser vistas como outras variáveis ​​protegidas pelo semáforo.

Se a manivela estiver levantada e a ponte estiver aberta, o uso exclusivo do cruzamento água / estrada é dado ao navio ou navios que passam pelo canal de água. Quando a maçaneta está abaixada, a ponte é fechada, e o uso exclusivo do cruzamento água / estrada é dado aos carros que passam pela ponte.

Publicidade

A variável semáforo pode controlar o acesso a outro conjunto de variáveis. Por exemplo, o estado up do identificador impede que as variáveis ​​$ cars_per_minute e $ car_toll_booth_total_income sejam atualizadas, etc.

Podemos levar o exemplo um pouco mais longe e esclarecer que quando a manivela opera para cima ou para baixo, uma luz correspondente é visível para os capitães dos navios na água e para as pessoas que dirigem carros e caminhões na estrada: todos os operadores podem ler um variável comum para um estado específico.

Embora o cenário descrito aqui não seja apenas um semáforo, mas também um mutex simples. Um mutex é outra construção de programação comum muito semelhante a um semáforo, com a condição adicional de que um mutex só pode ser desbloqueado pela mesma tarefa ou thread que o bloqueou. Mutex significa “ mutuamente exclusivo. ”

Neste caso, isso se aplica ao nosso exemplo, pois o operador de ponte é o único com controle sobre nosso semáforo e identificador mutex up / down. Em contraste, se a guarita no final da ponte tivesse um interruptor de cancelamento de ponte, ainda teríamos uma configuração de semáforo, mas não um mutex.

Ambas as construções são usadas regularmente na programação de computadores quando vários threads são usados ​​para garantir que apenas um único processo ou tarefa acesse um determinado recurso em nenhum momento. Alguns semáforos podem ser de comutação ultrarrápida, por exemplo, quando empregados em software de negociação multithread do mercado financeiro, e alguns podem ser muito mais lentos, mudando de estado apenas a cada poucos minutos, como quando usados ​​em uma ponte levadiça automatizada ou em um cruzamento de trem rodoviário. / p>

Agora que entendemos melhor os semáforos, vamos implementar um no Bash.

Implementando um semáforo no Bash: fácil ou não?

Implementar um semáforo no Bash é tão fácil que pode até ser feito diretamente da linha de comando, ou assim parece …

Vamos começar com simplicidade.

 BRIDGE = up if ["$" = "down"]; em seguida, echo "Carros podem passar!"; else echo "Os navios podem passar!"; fi BRIDGE = para baixo se ["$" = "para baixo"]; em seguida, echo "Carros podem passar!"; else echo "Os navios podem passar!"; fi 

Publicidade

Neste código, a variável BRIDGE mantém nosso status de ponte. Quando o configuramos para cima, os navios podem passar e quando o colocamos para baixo, os carros podem passar. Também podemos ler o valor de nossa variável em qualquer ponto para ver se a ponte está realmente para cima ou para baixo. O recurso compartilhado / comum, neste caso, é a nossa ponte.

No entanto, este exemplo é de thread único e, portanto, nunca nos deparamos com uma situação necessária de semáforo. Outra maneira de pensar sobre isso é que nossa variável nunca pode ficar para cima e para baixo no mesmo ponto no tempo em que o código é executado sequencialmente, ou seja, passo a passo.

Outra coisa a notar é que não controlamos realmente o acesso a outra variável (como um semáforo normalmente faria) e, portanto, nossa variável BRIDGE não é realmente uma variável de semáforo verdadeira, embora chegue perto.

Finalmente, assim que introduzirmos vários tópicos que podem afetar a variável BRIDGE, teremos problemas. Por exemplo, e se, diretamente após o comando BRIDGE = up, outro thread emitir BRIDGE = down, o que resultaria na mensagem Carros podem passar! saída, embora o primeiro segmento esperasse que a ponte estivesse levantada e, na realidade, a ponte ainda está se movendo. Perigoso!

Você pode ver como as coisas podem rapidamente se tornar obscuras e confusas, para não mencionar complexas, ao trabalhar com vários threads.

Publicidade

A situação em que vários tópicos tentam e atualizam a mesma variável ao mesmo tempo ou pelo menos perto o suficiente para outro tópico entender a situação errada (que no caso de pontes levadiças podem demorar um pouco) é chamado de condição de corrida: dois encadeamentos correndo para atualizar ou relatar alguma variável ou status, com o resultado de um ou mais encadeamentos podem estar errados.

Podemos tornar este código muito melhor fluindo o código para os procedimentos e usando uma variável semáforo real que restringirá o acesso à nossa variável BRIDGE dependendo da situação.

Criando um semáforo Bash

Implementar um full multi-threaded, que é thread-safe (um termo de computação para descrever software que é thread-safe ou desenvolvido de tal forma que os threads não podem afetar negativamente / incorretamente uns aos outros quando não deveriam) não é uma tarefa fácil. Mesmo um programa bem escrito que emprega semáforos não é garantido como totalmente seguro para threads.

Quanto mais tópicos houver, e quanto maior a frequência e complexidade das interações dos tópicos, mais provável será que haja condições de corrida.

Para nosso pequeno exemplo, veremos como definir um semáforo Bash quando um dos operadores de ponte levadiça abaixa uma alça da ponte, indicando, portanto, que ele ou ela deseja abaixar a ponte. Os leitores ávidos devem ter notado a referência a operadores em vez de operador: agora existem vários operadores que podem abaixar a ponte. Em outras palavras, existem vários threads ou tarefas que são executadas ao mesmo tempo.

 #! / bin / bash BRIDGE_SEMAPHORE = 0 lower_bridge () {# Um operador coloca uma das alças de operação da ponte para baixo (como um novo estado). # Suponha que foi previamente acordado entre os operadores que assim que um dos operadores # move um identificador de operação de ponte que seu comando deve ser executado, mais cedo ou mais tarde # portanto, iniciamos um loop que irá aguardar a ponte ficar disponível para movimento enquanto verdadeiro; faça if ["$" -eq 1]; em seguida, ecoe "Semáforo de ponte bloqueado, ponte em movimento ou outro problema. Esperando 2 minutos antes de verificar novamente." sleep 120 continue # Continue loop elif ["$" -eq 0]; then echo "Abaixe o comando da ponte aceito, travando o semáforo e abaixando a ponte." BRIDGE_SEMAPHORE = 1 execute_lower_bridge wait_for_bridge_to_come_down BRIDGE = 'down' echo "Ponte abaixada, garantindo que pelo menos 5 minutos passem antes do próximo movimento permitido da ponte." sleep 300 echo "5 minutos se passaram, desbloqueando o semáforo (liberando o controle da ponte)" BRIDGE_SEMAPHORE = 0 break # Exit loop fi done} 

Aqui temos uma função lower_bridge que fará uma série de coisas. Em primeiro lugar, vamos supor que outro operador mudou recentemente a ponte no último minuto. Como tal, há outro thread executando código em uma função semelhante a esta chamada raise_bridge.

Publicidade

Na verdade, essa função terminou de aumentar a ponte, mas instituiu uma espera obrigatória de 5 minutos que todos os operadores concordaram anteriormente e que foi codificada no código-fonte: ela impede que a ponte suba / desça o tempo todo . Você também pode ver esta espera obrigatória de 5 minutos implementada nesta função como sleep 300.

Então, quando a função raise_bridge estiver operando, ela terá definido a variável do semáforo BRIDGE_SEMAPHORE para 1, assim como fazemos no código aqui (diretamente após o comando echo "Abaixe o comando da ponte aceito, travando o semáforo e baixando a ponte") e – por meio da primeira verificação condicional se neste código – o loop infinito presente nesta função continuará (ref continue no código) em loop, com pausas de 2 minutos, pois a variável BRIDGE_SEMAPHORE é 1.

Assim que a função raise_bridge terminar de levantar a ponte e terminar seus cinco minutos de hibernação, ela definirá BRIDGE_SEMAPHORE para 0, permitindo que nossa função lower_bridge comece a executar as funções execute_lower_bridge e subsequentes wait_for_bridge_to_come_down embora tenha primeiro travado novamente nosso semáforo em 1 para evitar que outras funções assumam o controle da ponte.

Existem, no entanto, lacunas neste código e as condições de corrida que podem ter consequências de longo alcance para os operadores de ponte são possíveis. Você consegue identificar algum?

O "Comando da ponte inferior aceito, semáforo de bloqueio e ponte de abaixamento" não é seguro para threads!

Se outro thread, por exemplo, raise_bridge, estiver executando ao mesmo tempo e tentando acessar a variável BRIDGE_SEMAPHORE, pode ser (quando BRIDGE_SEMAPHORE = 0 e ambos os threads em execução atingirem seus respectivos echo & # 8216; s ao mesmo tempo que os operadores da ponte veem “ Comando abaixar ponte aceito, travando semáforo e abaixando ponte ” e “ Comando levantar ponte aceito, travando semáforo e levantando ponte ”. Diretamente um após o outro na tela! Assustador, não?

Publicidade

Mais assustador ainda é o fato de que ambos os threads podem prosseguir para BRIDGE_SEMAPHORE = 1, e ambos os threads podem continuar em execução! (Não há nada que os impeça de fazer isso). A razão é que ainda não há muita proteção para esses cenários. Embora esse código implemente um semáforo, ele não é seguro para threads. Conforme declarado, a codificação multithread é complexa e requer muito conhecimento.

Embora o tempo necessário neste caso seja mínimo (1-2 linhas de código levam apenas alguns milissegundos para serem executadas), e dado o provável baixo número de operadores de ponte, a possibilidade deste acontecimento é muito pequeno. No entanto, o fato de ser possível é o que o torna perigoso. Criar código thread-safe no Bash não é uma tarefa fácil.

Isso poderia ser melhorado, por exemplo, introduzindo um pré-bloqueio e / ou introduzindo alguma forma de atraso com uma nova verificação subsequente (embora isso provavelmente exija uma variável adicional) ou fazendo uma nova verificação regular antes execução real da ponte, etc. Outra opção é criar uma fila de prioridade ou uma variável de contador que verifica quantos threads bloquearam o controle da ponte, etc.

Outra abordagem comumente usada, por exemplo, ao executar vários scripts bash que podem interagir, é usar mkdir ou flock como operações de bloqueio de base. Existem vários exemplos de como implementar estes disponíveis online, por exemplo, Quais comandos Unix podem ser usados ​​como um semáforo / bloqueio ?.

Concluindo

Neste artigo, damos uma olhada no que é um semáforo. Também tocamos brevemente no assunto de mutex. Finalmente, vimos a implementação de um semáforo no Bash usando o exemplo prático de vários operadores de ponte operando uma ponte móvel / ponte levadiça. Também exploramos como é complexa a implementação de uma solução confiável baseada em semáforo.

Se você gostou de ler este artigo, dê uma olhada em nossas Declarações, Erros e Falhas: Qual é a diferença? artigo.

Nenhum comentário