AsyncIO Micropython: métodos de sincronização em programação assíncrona

Na programação sequencial, encontro constantemente o desejo óbvio de não interromper o programa em um momento em que o objetivo de tarefas individuais (processos) é de ações periódicas - por exemplo, pesquisar valores de sensores ou transmitir dados em uma programação para um servidor ou inserir / enviar uma grande quantidade de dados. A coisa mais simples, é claro, é aguardar a conclusão do evento periódico e, lentamente, continuar a executar outras tarefas.

while True: do_ext_proc_before() do_internal_proc() sleep(5) do_ext_proc_after() 

Você pode abandonar 'sleep ()' e ativar a verificação de algumas condições no loop, o que permitirá que você não atrase o loop principal pelo menos até um evento periódico:

  start = time() set_timer(start,wait=5) #   set_timeout(start,wait_to=7) #   set_irq(alarm) #    while True: curTime = time() do_ext_proc_before() if timer(curTime) or timeout(curTime) or alarm: # if all events crazy start simultaneously - reset all start = time() set_timer(start,wait=5) #   set_timeout(start,wait_to=7) #   set_irq(alarm) #    do_internal_proc() do_ext_proc_after() 

Na programação assíncrona, cada tarefa se torna um processo independente e é executada, dependendo da implementação específica, em paralelo ou pseudo-paralelo, usando um entendimento interno das condições de espera naturais ou artificialmente estabelecidas ou o uso de um recurso limitado, por exemplo, um disco ou um canal de comunicação.

  setTask(do_ext_proc_before()) setTask(do_internal_proc(),timer=5,timeout=7,alarm_handler=alarm) setTask(do_ext_proc_after()) runTasks() 

Agora, surge um problema que não existe na programação sequencial - o que fazer se for necessário sincronizar alguns processos com seus assíncronos
fazendo? Por exemplo, tendo recebido dados de sensores, inicie o processo de envio de dados para um servidor ou responda a uma emergência. Além disso, na programação assíncrona, a organização da entrada / saída assíncrona é organicamente resolvida no padrão da linguagem e outras situações são resolvidas nas bibliotecas.

Estudei esta questão usando a biblioteca estendida assíncrona Micropython publicada
Peter Hinch ( https://github.com/peterhinch/micropython-async/blob/master/TUTORIAL.md )
A solução mais simples é sinalizar o evento para os processos interessados. Para fazer isso, use a classe Event (), que contém vários módulos

  Event.Set( timeout = None, data = None ) -    (Event = True), ,     , Event.IsSet() - ,   ,  True,    False   Event.Wait() -   ,     - Done,Timeout,Cancel Event.Data() -  ,     Event.Clear() -   (Event = False). 

A conclusão é registrada, via de regra, pelo processo que aguarda a ocorrência do evento, por exemplo, o processo de exibição na tela ou o processo de salvar dados no disco ou o tempo limite, então não há necessidade de atualizar ou salvar os dados, pois eles não são atualizados por qualquer motivo ou devido à sua interrupção na ocorrência de outro evento importante, por exemplo, a transição para o modo de suspensão ou reinicialização, que pode exigir a liberação de todos os processos pendentes, redefinindo os eventos correspondentes.

Deve-se ter em mente que é aconselhável tornar Event.Clear () com apenas um processo, se isso não contradizer o algoritmo fornecido. Caso contrário, se vários processos estiverem aguardando o evento Event.Set () ocorrer, presume-se que Event.Clear () deva ser executado por um dos processos interessados, apenas garantindo que todos os processos interessados ​​tenham respondido ao evento. Isso complica a lógica de decisão ao usar a Classe de Eventos ao aguardar um evento por vários processos. Essa situação é resolvida estabelecendo uma certa quantidade de Clear () do evento que ocorreu.

  Barrier.Set( quantity = 1, timeout = None, data = None ) - quantity = 1  Event.Set() Barrier.IsSet() - ,   ,  True,    False   Barrier.Wait() -   ,     - Done,Timeout,Cancel Barrier.Data() -  ,     Barrier.qty -      Barrier.Clear() -   (Event = False),        Barrier.quantity  ,    ,     

Ao mesmo tempo, nenhuma contabilidade é mantida - qual processo específico já respondeu e ainda não, o que pode dar origem ao problema de reagir novamente ao evento, se isso for essencial para um determinado algoritmo. Se, em vez de Barrier.quantity, você passar uma lista de nomes de processos interessados, esse conflito poderá ser evitado. Além disso, no caso de um tempo limite ou interrupção do evento, você pode determinar quais processos pendentes específicos ainda não funcionaram. Todos os itens acima se aplicam a uma situação em que um ou mais processos aguardam a ocorrência de um determinado evento ou uma situação de um para muitos. Isso ocorre quando o processo ou processos do_ext_proc_after () durante a programação seqüencial somente serão executados após a conclusão de do_internal_proc (). Para facilitar o entendimento, expandiremos a Classe de Evento e Classe de Barreira existente para a nova Classe EEvent e a tornaremos ou objetos gerados por ela - globais. Aqui 'criadores' é o nome ou a lista de nomes de processos que acionam o evento ou desbloqueiam o recurso, 'seguidores' é o nome ou a lista de nomes de processos que aguardam o evento ou desbloqueiam o recurso

  EEvent.Set (creators, folowers, timeout = None, data = None ) -  True,        EEvent.IsSet( procName ) - procName -   ID   EEvent.Wait( procName ) EEvent.Clear( procName ) EEvent.Folowers() -    ,      . Barrier.qty = len(EEvent.List()) EEvent.Creators() -   ,     

Usando os módulos da classe EEvent, podemos descrever a solução para o problema discutido anteriormente.

  def do_internal_proc(): ... EEvent.Set ('p_Creator',('p_Folwer1','p_Folwer2')) # exec 'p_Folwer1','p_Folwer2' after event is come in 'p_Creator' ... def do_ext_proc_after1() ... EEvent.Wait('p_Creator') ... EEvent.Clear('p_Folwer1') def do_ext_proc_after1() ... EEvent.Wait('p_Creator') ... EEvent.Clear('p_Folwer2') 

Considere a situação oposta - quando um processo está aguardando a conclusão de vários eventos ou uma situação "muitos para um". Em outras palavras, se a execução de do_internal_proc () puder ocorrer somente após a execução de do_ext_proc_before (). No caso extremo, quando um processo estiver aguardando a conclusão / ocorrência de um evento, a tarefa poderá ser resolvida usando a classe Event. Quando a conclusão de vários eventos é esperada, por exemplo, somente após a exibição dos dados recebidos e o envio ao servidor, salvando-os em disco, é necessário que cada processo executado estabeleça sua participação no evento esperado e aguarde até que todos os processos sejam concluídos.

  def do_ext_proc_before1() ... EEvent.Set('p_Creator1','p_Folwer') ... def do_ext_proc_before2() ... EEvent.Set('p_Creator2','p_Folwer') ... def do_internal_proc(): ... EEvent.Wait(('p_Creator1','p_Creator2')) ... EEvent.Clear('p_Folwer') 

Outro aspecto importante da programação assíncrona é o compartilhamento de um recurso limitado. Por exemplo, a atualização de dados deve ser realizada por apenas um processo; o restante dos processos que reivindicam uma ação semelhante deve enfileirar ou aguardar até que os dados sejam atualizados. Ao mesmo tempo, é possível que a leitura de dados para exibição ou encaminhamento não seja crítica. Portanto, é necessário conhecer a lista de processos concorrentes ao organizar eventos relevantes.

No padrão de programação assíncrona, essa tarefa é resolvida pelos módulos da classe Lock. No caso geral, o problema também pode ser resolvido de maneira semelhante à situação um para muitos.

  def do_internal_proc(): # lock activity all 'followers' in list ... EEvent.Set ('p_Creator',('p_Folwer1','p_Folwer2')) # exec 'p_Folwer1','p_Folwer2' after event is come in 'p_Creator' ... def do_ext_proc_after1() ... EEvent.Wait('p_Creator') # waiting for recourse releale if ( EEvent.Set ('p_Folwer1','p_Folwer2')): # lock resource 'p_Folower1' now is 'p_Creator' ... else: EEvent.Wait('p_Folower2') # continue waiting for recourse releale ... EEvent.Clear('p_Folwer1') # releafe recourse def do_ext_proc_after1() ... EEvent.Wait('p_Creator') if ( EEvent.Set ('p_Folwer2','p_Folwer1')): # lock resource 'p_Folower2' now is 'p_Creator' ... else: EEvent.Wait('p_Folower1') # continue waiting for recourse releale ... EEvent.Clear('p_Folwer2') # releafe recourse 

Além das opções consideradas, existem soluções que limitam a produtividade, organizam filas e agendamento controlado de processos, mas em minha atividade ainda não há necessidade disso e, como resultado, uma necessidade de entendimento suficiente para mim, embora não exclua que haja mais elegância ou decisões econômicas.

Concluindo, quero dizer que as abordagens seqüenciais e assíncronas têm o mesmo direito de existir e implementar com sucesso os algoritmos fornecidos. Portanto, a aplicação dessa ou daquela abordagem é determinada pelas prioridades do criador - o que é mais importante para ele na implementação dos algoritmos fornecidos - transparência e legibilidade, velocidade ou volume do código resultante.

Source: https://habr.com/ru/post/pt442268/


All Articles