Sincronização



Esta secção discute em algum detalhe a maneira como agentes (domínios de execução) distintos interagem entre si no caso frequente em que vários agentes executam código em simultâneo (execução paralela), e os possíveis problemas (falhas de consistência) que podem resultar daí.

Basicamente todas as propriedades (estado) de um modelo são determinadas pelo conteúdo das variáveis globais e de agente (turtle e patch), e, portanto, o modelo evoluí à medida que estes conteúdos vão sendo modificados pela execução dos agentes. Num programa de computador convencional, existe usualmente apenas uma linha de execução, ou seja, todas as instruções do programa são executadas uma de cada vez, em série, o que elimina a possibilidade de ocorrerem falhas de consistência provocadas por interacções inesperadas entre múltiplas linhas de execução que acedem às mesmas variáveis.

No NetLogo, no entanto, é possível, em qualquer instante, ter um número arbitrário de blocos de código (invocados por botões de interface ou pela primitiva ask) a executar, possivelmente tantos quanto o número de agentes em existência.

Digamos que um modelo possuí um botão, Executar, associado a um procedimento de observer com o mesmo nome. Se esse for o único botão premido (é possível ter mais que um botão activado de cada vez), então o procedimento Executar está a ser executado pelo observer, e enquanto este procedimento não chamar outro ou usar a primitiva ask, está-se numa situação em tudo semelhante à de um programa convencional, isto é, apenas existe uma linha de execução.
Suponha-se que a dada altura o procedimento Executar chega à instrução ask turtles [ Interagir ]. Nessa situação, a execução no domínio do observer fica temporariamente suspensa, mas são criadas tantas novas linhas de execução quanto o número de turtles em existência, e todas essas linhas passam a executar em paralelo o procedimento Interagir.

O que significa executar em paralelo? Significa que o NetLogo alterna continuamente e de maneira transparente (imperceptível para o utilizador) entre os vários agentes que estão a executar. Esta alternância (dependendo do número de agentes) pode ser levada a cabo muitas vezes em cada segundo, de maneira a dar a impressão ao utilizador que todos os agentes trabalham "ao mesmo tempo".

Digamos que no nosso modelo existem 100 turtles, 50 vermelhas e 50 amarelas, e que a acção consiste em fazer mover as turtles aleatoriamente na janela gráfica, chamando repetidamente, para todas as turtles, o procedimento Interagir, que tenta trocar a cor da turtle actual com a cor de outra turtle presente no mesmo patch:

to Interagir
  locals [ temp alvo ]
  set alvo random-one-of other-turtles-here
  if alvo != nobody
  [ set temp color-of alvo
    set color-of alvo color
    set color temp ]
end

Como o modelo se baseia em troca de cores, é de esperar que o número de turtles vermelhas e turtles amarelas se mantenha constante ao longo de toda a execução, 50 para cada lado. No entanto, se o modelo for programado como descrito e depois executado, por exemplo, associando ao botão Executar (do tipo forever) o seguinte procedimento:

to Executar
  ask turtles
  [ rt random-float 90
    lt random-float 90
    forward 1 ]
  ask turtles [ Interagir ]
end

O que se verificaria é que, ao longo da execução, a esperada conservação do número de cores seria violada, isto é, apesar do número total de turtles se manter constante, poderiam começar a aparecer mais turtles vermelhas e a desaparecerem amarelas, ou vice-versa. Isto deve-se a um problema de sincronização no procedimento Interagir: Apesar de poder parecer bem programado (se todo o modelo corresse com uma única linha de execução), a verdade é que num ambiente de execução paralela, como o NetLogo, é preciso ter mais cuidado. Analisemos o código existente:

No procedimento Executar, primeiro pede-se às turtles (o primeiro ask) que executem um passo de uma marcha aleatória (rodarem numa direcção aleatória e avançarem um passo). Apesar de todas as turtles avançarem ao mesmo tempo, fazem-no de maneira independente (a marcha de uma turtle não interfere com nenhuma propriedade de qualquer outro agente), portanto não será aqui que poderá estar o problema.

Quando um agente (incluíndo o observer) pede a outro agente ou grupo de agentes que execute código usando a primitiva ask, o agente que fez o pedido é posto em espera (suspenso) até que todos os agentes requisitados terminem a execução do bloco de código incluído no interior da primitiva ask. No entanto, os agentes requisitados executam em paralelo, o que significa que quando um determinado agente acaba uma instrução, a execução passa para o agente seguinte dentro do grupo especificado na primitiva ask, e assim sucessivamente, voltando ao primeiro agente (próxima instrução no bloco), até que todos os agentes cheguem ao fim do bloco.
O que isto significa é que o NetLogo apenas garante que instruções individuais são atómicas, isto é, um agente nunca é interrompido a meio de uma instrução. No entanto, usualmente, um agente é constantemente interrompido enquanto executa um bloco de várias instruções, para dar lugar a outros agentes.

Voltando ao procedimento Executar, como o passo da marcha aleatória e o procedimento Interagir estão em primitivas ask diferentes, primeiro as turtles avançam todas, e só depois tentam, simultaneamente, trocar de cor com outra turtle que esteja no mesmo patch. E é aqui que está o problema.

Suponha-se que num determinado patch estão exactamente duas turtles, A (vermelha) e B (amarela). Digamos que a turtle A começa a executar o procedimento Interagir e é interrompida imediatamente a seguir a executar a instrução set temp color-of alvo (ver acima). Neste caso, alvo refere-se necessariamente à turtle B, ou seja, temp fica com a cor amarela.

A execução passa então para a turtle B (podendo até ter passado por outras turtles entre A e B, o que é irrelevante neste caso). Se esta turtle executar o procedimento até ao fim, terá trocado de cor com a turtle A, ou seja, A é agora amarela e B é vermelha. Quando a execução volta eventualmente à turtle A, esta continua com a instrução set color-of alvo color, que torna a por a cor de B amarela (igual à de A). Mas, de seguida, a instrução set color temp põe a cor de A igual à variável temp (que foi definida na execução anterior), ou seja, amarela. Isto é, no fim desta iteração, tanto A como B ficaram amarelas, ou seja, "criou-se" uma turtle amarela e "perdeu-se" uma turtle vermelha.

Pode parecer bastante fortuito que a sequência exacta de acontecimentos tal como descritos acima possa acontecer, mas ao longo de uma execução podem ser feitas milhões de iterações para cada agente, o que torna praticamente garantido que esta sequência acontecerá pelo menos uma vez, e basta uma vez para comprometer a integridade do modelo (neste caso, a conservação do número de cores). Além disso, podem também ocorrer outras sequências de eventos, mais complexas e igualmente com efeitos não previstos.

O problema descrito pode ser resolvido "protegendo" a execução de cada agente com a primitiva without-interruption, que transforma o bloco de código por ela contido numa operação atómica, isto é, ininterruptível. Por exemplo, modificando a segunda instrução do procedimento Executar para:
ask turtles [ without-interruption [ Interagir ] ]
Isto tem o efeito de sequencializar a execução dos agentes, ou seja, as turtles passam a executar o procedimento Interagir umas a seguir às outras, em vez de todas ao mesmo tempo.

Note-se que without-interruption deve ser usado apenas onde necessário (onde existe a possibilidade de interacções indesejadas entre agentes), e não se deve "sequencializar" todo um modelo envolvendo todo o código para cada agente em without-interruption, pois o paralelismo é normalmente desejável para a maior parte das interacções entre agentes.

Certas primitivas (como hatch) implicitamente protegem o código que incluem com without-interruption, ou seja, trabalham de maneira distinta de ask (que, por defeito, sempre executa em paralelo).

O exemplo usado nesta secção para ilustrar um problema decorrente de uma falha de sincronização entre agentes (não conservação do número de cores) é bastante comum: Se num determinado modelo existe uma quantidade que pode variar entre agentes, mas que deveria ser globalmente conservada, e essa conservação está a ser violada, sem que haja uma razão "óbvia" no código do modelo, então é muito provável que o problema se deva a uma falha de sincronização semelhante à aqui descrita.