Os semáforos e os sinais são ferramentas fornecidas pela linguagem 4D para gerenciar as interações e evitar conflitos entre processos em uma aplicação multiprocesso. Satisfazem diferentes necessidades:
um semáforo lhe permite ter certeza de que dois ou mais processos não modifiquem o mesmo recurso ao mesmo tempo. Só o processo que define o semáforo pode eliminá-lo.
um sinal lhe permite ter certeza de que um ou mais processos esperarão que se complete uma tarefa específica antes de continuar com sua execução. Qualquer processo pode esperar ou liberar um sinal.
Em um programa de computador, um semáforo é uma ferramenta que é usada para proteger as ações que devem ser realizadas por um único processo ou usuário por vez.
Em 4D, a necessidade convencional de uso de um semáforo é para modificar um array inter-processo: se um processo estiver modificando os valores do array, outro processo não deve poder fazer a mesma coisa ao mesmo tempo. O desenvolvedor utiliza um semáforo para indicar a um processo que só pode realizar sua sequência de operações se nenhum outro processo estiver realizando as mesmas tarefas. Quando um processo encontra com um semáforo, há três possibilidades:
Obtém imediatamente o direito a passar
Espera seu turno até que obtém o direito a passar
Continua seu caminho, abandonando a ideia de realizar as tarefas.
Portanto, o semáforo protege partes do código. Se permite passar só um processo de cada vez e bloqueia o acesso até que o processo que possui atualmente o direito de uso renuncie a este direito, liberando o semáforo.
Em4D, um semáforoé estabelecido chamando a funçãoSemaphore. Para liberarum semáforo,se chama ocomandoCLEAR SEMAPHORE.
A função Semaphore tem umcomportamento muitoespecial já que realiza potencialmenteduas ações de cada vez:
Se o semáforojá estiver assignado,a funçãodevolveTrue
Se não for assignado o semáforo, a função oassignaao processo e devolveFalseao mesmo tempo.
Esta ação dupla realizada pelomesmo comandoasseguraque nenhuma operaçãoexternapode ser inseridaentre a probado semáforo e sua atribuição.
Pode utilizar o comandoTest semaphorepara saber seum semáforo já estiver assignado ou não.Este comandose utiliza principalmente comoparte das operaçõeslongas, taiscomo o fechamento anual das contas ondeTest semaphorelhe permite controlara interfacepara evitar o acessoa certasoperações taiscomo a adição dos dados contáveis.
Os semáforos devem ser utilizados respeitando os princípios a seguir:
um semáforo deve ser definido e lançado no mesmo método,
a execução de código protegido pelo semáforo deve ser a mas curta possível,
o código deve ser temporizado por meio do parâmetro contTics da função Semaphore para esperar a liberação do semáforo.
Este é o código típico para o uso de um semáforo:
While(Semaphore("MySemaphore";300)) IDLE End while // colocar aqui o código protegido pelo semáforo CLEAR SEMAPHORE("MySemaphore")
Um semáforo que não se libera pode bloquear parte do banco de dados. Configurar e liberar o semáforo no mesmo método ajuda a eliminar este risco.
Minimizar o código protegido pelo semáforo aumenta a fluidez da aplicação e evita que o semáforo seja um gargalo.
Por último, utilizando o parâmetro opcional contTics do comando Semaphore é essencial para otimizar a espera do semáforo a liberar. Utilizando este parâmetro, os comandos funcionam da maneira abaixo:
O processo espera um número máximo especificado de tics (300 no exemplo) para que o semáforo esteja disponível, sem a execução de código passar para a próxima linha,
Se o semáforo for liberado antes do final deste limite, ele é atribuído imediatamente ao processo (Semaphore devolve False) e se reinicia a execução de código,
Se o semáforo não for liberado antes do final deste limite, a execução do código recomeça.
O comando também dá prioridade às petições estabelecendo uma fila. Desta maneira, o primeiro processo que solicitar um semáforo será o primeiro em obter um.
Lembre que o tempo de espera se estabelece em função das características específicas da aplicação.
Há dois tipos de semáforos em 4D: semáforos locais e semáforos globais.
Um semáforo local é visível para todos os processos de um mesmo posto e só no posto. Um semáforo local pode ser criado ao adicionar um prefixo ao nome do semáforo um sinal de dólar ($). Se utilizar semáforos locais para supervisar as operações entre os diferentes processos que são executados na mesma máquina. Por exemplo, um semáforo local pode ser utilizado para controlar o acesso a um array interprocesso compartido por todos os processos de um banco de dados mono usuário ou de um equipo cliente.
Um semáforo global é acessível a todos os usuários e todos seus processos. Os semáforos globais são utilizados para controlar as operações entre os usuários de um banco de dados multi-usuário.
Os semáforos globais e locais são idênticos em sua lógica. A diferença está em seu alcance.
No modo cliente-servidor, os semáforos globais são compartidos entre todos os processos que são executados em todos os clientes e servidores. Um semáforo local somente se comparte entre os processos que se executam na máquina onde foi criado.
Em 4D, os semáforos globais ou locais têm o mesmo alcance, já que você é o único usuario. Entretanto, se seu banco de dados estiver sendo usado em ambas configurações, tenha certeza de usar semáforos globais ou locais, dependendo do que quiser fazer.
Nota: é recomendado o uso de semáforos locais quando precisar de um semáforo para gerenciar um aspecto local para um cliente da aplicação, tais como a interface ou um conjunto de variáveis inter processo. Se usar um semáforo global neste caso, não apenas haveria intercâmbios de rede desnecessários, mas também poderia afetar a outras máquinas clientes desnecessariamente. O uso de um semáforo local evitaria estes efeitos secundários indesejáveis.
Um sinal é um objeto compartido que contém dois métodos integrados, signal.wait( ) e signal.trigger( ), que devem ser passados como um parâmetro aos comandos que chamam ou criam workers ou processos.
Qualquer worker/processo chamando o método signal.wait( ) do sinal vai parar sua execução até que a propriedade signal.signaled vire true. Enquanto espera por um sinal, o processo de chamada não usa qualquer cpu, o que é bem interessante para performance em aplicações multiprocesso. A propriedade signal.signaled vira true quando qualquer worker/processo chama o método signal.trigger( ) do sinal.
Note que para evitar situações de bloqueio, signal.wait( ) também pode retornar depois que um timeout tiver sido alcançado.
O diagrama abaixo ilustra como um objeto sinal pode ser usado:
Em 4D, pode criar um novo objeto sinal ao chamar a função New signal. Ao ser criado, este sinal deve ser passado como um parâmetro aos comandos New process ou CALL WORKER de modo que possam modificá-lo quando tiverem terminado a tarefa que você quer que esperem.
signal.wait( ) deve ser chamado do worker/processo que precisa de outro worker/processo para terminar a tarefa a continuar.
signal.trigger( ) deve ser chamado do worker/processo que termina sua execução para lançar todos os outros
Uma vez que o sinal tiver sido lançado usando signal.trigger( ) (uma propriedade signal.signaled é true), não pode ser reutilizado novamente. Se quiser estabelecer outro sinal, pode precisar chamar a função New signal novamente.
Já que um objeto sinal é um objeto compartido (see Objetos compartidos e Coleções compartidas), pode usá-lo para retornar resultdos de workers/processos chamados, desde que não esqueça de escrever valores dentro de uma estrutura Use...End use (ver exemplo).
Um processo preemptivo precisa obter um valor de usuário (de um diálogo por exemplo). Já que não é permitido exibir um diálogo do processo preemptivo, ele chama um processo cooperativo e espera pelo valor retornado:
// chama o proceso principal e executa o método OpenForm CALL WORKER(1;"OpenForm";$signal) // faz outro cálculo
... // Espera o final do processo $signaled:=$signal.wait()
// Processa os resultados $calc:=$signal.result+...