Introdução a programação concorrente e atores

Samuel Brasileiro
Apple Developer Academy | UFPE
4 min readJun 19, 2021

Muitas vezes quando vamos programar encontramos alguns desafios, como baixar e enviar dados da internet, processar imagens, realizar cálculos complexos e executar animações. Quando eu comecei ser desenvolvedor, uma problema aparecia na minha cabeça sobre todos esses processos e vários outros parecidos: O programa sempre travava por um tempinho, porque essas atividades sempre necessitavam de segundos para serem processadas. Mas como eu poderia resolver isso?

Foi assim que eu descobri o conceito de programação concorrente e threads. O programa pode possuir vários fios de execução (thread) paralelos à thread original, conseguindo realizar aqueles processos demorados em um fio diferente sem comprometer o funcionamento geral da aplicação! Entenda com o desenho abaixo:

Embora pareça simples, a programação concorrente tem que ter muitos cuidados para não ocorrer nenhum problema. Mas o que de ruim pode acontecer? Por exemplo, se o seu programa se divide em dois ou mais fios de execução diferentes, o tempo de executar cada fio vai sempre ser diferente. Em um fio você pode estar pegando informações na internet (pode ser bem demorado) e no outro você pode estar apenas somando dois números, que é bem mais rápido. Se para continuar o programa você precisa que todas as threads terminem, é necessário criar algum artifício de sincronização para evitar isso.

Outro problema bastante comum é a concorrência por dados. Imagine que você tem vários fios que executam atividades com a mesma variável: alguns fios leem essa variável e outros a modificam. Dessa forma, pode ocorrer inconsistências, um fio pode ler uma variável antes da hora de ele ser modificado ou ele pode ser lido e modificado ao mesmo tempo, ocasionando um bug.

Para consertar todos esses problemas existem vários artifícios, como Locks e Semáforos. Mas agora, na WWDC, foi apresentado o conceito de actors(atores) em Swift.

O que é um ator?

Ele provém a sincronização de um estado e isola o acesso a esse estado por todo o programa. Uma variável ator ao ser acessada automaticamente já se garante que esse acesso vai ser mutualmente exclusivo.

Um actor é um novo tipo de referência, assim como uma struct e uma classe. Ele pode possuir variáveis e métodos, usarem protocolos e extensões exatamente igual às classes e a diferença primária do tipo actor é que ele isola os dados da instância atual e o isola do resto do programa, garantido acesso sincronizado a esse dado.

Um exemplo possível do uso de actor é a situação abaixo:

Se duas threads usarem a função de incrementar, com esse objeto sendo uma struct ou uma classe, é potencialmente possível acontecer a concorrência por dados, que eventualmente levaria a um conflito no programa.

De outro modo, se esse objeto for do tipo actor, já vai ser garantido automaticamente que os seus dados não vão ser acessados e escritos concorrentemente, pois vai haver uma sincronização.

Agora e se eu precisar criar um método no meu actor que não possua essa sincronização? É só adicionar um prefixo chamado nonisolated para o método, como no exemplo abaixo:

É bom lembrar que em alguns momentos, ao utilizar funções e métodos que não são síncronos, o programa vai requisitar o uso do não-isolamento. Mas tenha em mente que, nesse método, ao ser não isolado, você não pode usar as variáveis isoladas do seu actor!

Mas eu tenho uma struct, não quero modificá-la para actor mas quero garantir a sincronicidade dela, como prosseguir? Para isso, você pode adicionar um novo protocolo ao sua struct: Sendable. Ao usar essa conformidade, você está garantindo ao programa que essa struct é segura de ser compartilhada de maneira concorrente.

Objetos do tipo sendable podem ser perfeitamente utilizadas em atribuições para actors sem apresentar erros. Funções também podem possuir o prefixo @Sendable, significando que é seguro atribuir o valor resultante da função entre atores.

Até agora é isso :)

Todas essas conformidades e várias outras foram criadas com o objetivo de ter o maior cuidado possível de não existir concorrência de dados em potencial, um dos maiores problemas que encontramos na programação concorrente. Para se aprofundar e detalhar muito mais no contexto da nova sincronização em swift aconselho assistir aos vídeos da WWDC 21!

--

--