Back home

Swift Concurrency Series 05|A diferença entre o Actor e os métodos tradicionais de gravação thread-safe

Os atores não são "fechaduras rápidas". O que realmente mudam é a forma como o estado partilhado é organizado.

Ao ouvir Actor pela primeira vez, você subconscientemente o entenderá como “uma ferramenta de segurança de thread fornecida pela Swift”.

Esse entendimento não está completamente errado, mas se você parar por aqui, será fácil escrever código que “usa atores, mas a estrutura ainda é confusa”.

Porque o mais importante do Actor é que ele muda a ideia padrão:

O estado mutável compartilhado não deve ser exposto primeiro a todos e depois encontrar maneiras de protegê-lo; uma abordagem mais razoável é isolá-lo primeiro e depois decidir como acessá-lo externamente.

Isso pode parecer uma diferença de palavras, mas na verdade é um divisor de águas nos hábitos de design de simultaneidade.

1. A coisa mais perigosa na simultaneidade geralmente é o estado mutável compartilhado

A maioria dos bugs de simultaneidade começa em:

  • Duas tarefas leem e gravam o mesmo cache ao mesmo tempo
  • Uma tarefa ainda não foi escrita e outra tarefa já fez um julgamento com base no valor antigo.
  • Várias páginas modificam um status global ao mesmo tempo
  • Um determinado serviço é responsável por atualizar tokens, consumir tokens e transmitir os resultados.

Esses problemas podem parecer surgir de várias formas, mas as causas básicas geralmente são as mesmas: **O estado mutável compartilhado não tem limites claros. **

Uma vez que os limites do estado não são claros, qualquer alteração na ordem de agendamento das tarefas pode amplificar os bugs.

2. O problema com as soluções tradicionais de segurança de thread é que elas não são centralizadas o suficiente.

As soluções que comumente usamos no passado incluem:

-NSLock

  • fila serial
  • bloqueio de leitura e gravação
  • Acordo da equipe de que “este objeto só pode ser acessado em uma determinada fila”

Nenhuma dessas soluções está errada e muitos sistemas maduros ainda estão em uso estável hoje. O verdadeiro problema deles é:

**As regras de acesso são facilmente dispersas. **

A princípio você ainda pode se lembrar:

  • Esta cache deve ser acessada com cadeado
  • Esse dicionário só pode ser alterado na fila serial
  • Um determinado estado só pode ser lido e escrito no thread principal

Mas à medida que o número de pontos de chamada aumenta, estas regras tornar-se-ão lentamente distorcidas. A coisa mais irritante sobre os bugs de simultaneidade é que muitas vezes não é mais possível garantir que toda a equipe cumpra estritamente essas regras dispersas.

3. A ideia central do Ator é “reduzir o compartilhamento primeiro”

Este é o ponto mais importante que vale a pena repetir.

A ideia tradicional é mais parecida com:

  1. Primeiro tenha um status compartilhado
  2. Em seguida, pense em maneiras de protegê-lo por meio de bloqueios, filas e convenções

A ideia do ator está mais próxima:

  1. Este estado não deve ser acessado arbitrariamente.
  2. Coloquei-o primeiro no limite de isolamento
  3. O mundo exterior só pode interagir com ele através da interface controlada

A maior mudança que isto traz é que somos forçados a pensar na propriedade do estatuto, em vez de concordar que todos possam tocá-lo directamente.

Por exemplo, se um coordenador de atualização de token for apenas um objeto global com vários bloqueios, a mentalidade padrão será “Todo mundo está usando esse estado e eu tenho que protegê-lo”. Se for Actor, a mentalidade padrão será “Este estado é gerenciado por ele e outros só podem solicitar resultados ou enviar comandos”.

É assim que o design muda.

4. Esta abordagem de isolamento é mais adequada para projetos complexos

Porque o que os projetos complexos mais temem é a proliferação de regras.

Uma vez que o acesso estatal for bloqueado pelo Ator, muitos problemas serão expostos anteriormente:

-Quem controla esses dados?

  • O requisito externo é um instantâneo, um valor derivado ou uma operação imperativa?
  • Quais operações devem ter semântica serial e quais são apenas consultas somente leitura

Esses problemas muitas vezes eram adiados no modelo antigo, porque todos presumiam “compartilhar primeiro e depois bloquear se houvesse problemas”. Os atores, por outro lado, forçam o desenho dos limites mais cedo.

Portanto, prefiro pensar no Actor como uma ferramenta de design de simultaneidade em vez de apenas uma ferramenta de segurança de thread.

5. Qual é o melhor lugar para colocar o ator?

Nem todos os objetos são dignos de serem transformados em Atores. É mais adequado para funções que atendam ao mesmo tempo as seguintes características:

  • Ter estado mutável compartilhado
  • Será acessado simultaneamente por múltiplas tarefas
  • Consistência é importante
  • Os métodos de acesso precisam ser coordenados centralmente

Exemplos típicos incluem:

  • Coordenador de cache de imagens
  • Coordenador de atualização de token
  • Baixe o centro de registro de tarefas
  • Algum tipo de acessador de recursos global
  • Central de mensagens que requer consumo serial de eventos

O que esses objetos têm em comum é que são centros de estado compartilhados entre tarefas e propensos a problemas de simultaneidade.

Se for apenas um serviço de função puro ou um objeto de estado usado apenas brevemente dentro de uma única página, um Ator pode não ser necessário.

6. Qual é a diferença essencial entre Ator e “Adicionar um bloqueio a toda a classe”?

Superficialmente, todos eles podem realizar “múltiplas tarefas sem alterar o status”. Mas a experiência em engenharia varia muito.

Ao bloquear uma turma inteira, as perguntas comuns são:

  • O chamador ainda pode obter muitos detalhes internos
  • A granularidade dos bloqueios pode facilmente ficar fora de controle
  • Alguns métodos chamam outros recursos de sincronização, formando aninhamentos complexos.
  • Os membros da equipe ainda podem ignorar as regras e acessar diretamente o status que não deveriam acessar

O ator, pelo menos no nível semântico, expressa claramente:

  • Este status não é compartilhado livremente
  • O acesso de isolamento cruzado precisa passar explicitamente pelo limite assíncrono
  • Acessar esse estado é em si um comportamento restrito

Em outras palavras, os atores não apenas “ajudam a adicionar proteção”, mas também tornam os métodos de acesso inseguros mais difíceis de escrever casualmente.

7. O ator não consegue resolver nada

Isto deve ficar claro. Ao aprender este conteúdo pela primeira vez, você terá a ilusão de que a segurança do thread será deixada para ele no futuro.

Na verdade, os Atores resolvem apenas um tipo de problema: isolar o estado compartilhado.

Não resolve:

  • A sequência de negócios em si é razoável?
  • As tarefas antigas devem continuar após sair da página?
  • Se o resultado expirou
  • A fronteira do estado está traçada incorretamente?

Por exemplo, se o cache de resultados da pesquisa for transformado em um ator, mas a página ainda permitir que solicitações antigas substituam novos resultados de palavras-chave, o bug ainda existirá. Porque o problema aqui é que a “validade do resultado” não é modelada em primeiro lugar.

Portanto, os atores são importantes, mas não são uma panacéia para a concorrência.

8. As duas direções de ator mais facilmente mal utilizadas

1. Tente encaixar tudo no Ator

Uma vez que os atores sejam considerados como “mais seguros desde que sejam usados”, eles começarão a empacotar demais:

  • O estado que obviamente é usado apenas em um contexto de thread único também é empacotado como um Ator
  • A lógica que é obviamente mais adequada para processamento de função pura também é forçada no Ator
  • No final, todo o sistema fica cheio de limites de acesso assíncronos e a estrutura fica mais pesada.

Mais atores nem sempre é melhor. A chave é colocá-los onde o estado compartilhado seja realmente necessário para ser isolado.

2. Trate os atores como “prova de segurança”

Depois que os atores forem adicionados a algum código, a equipe assumirá que esse trecho de código é “thread-safe”. Mas muitos bugs vêm de:

  • Projeto de ciclo de vida errado
  • Fluxo de estado errado
  • Relacionamento de tarefa errado

O ator pode garantir “Não mexa com a simultaneidade e mexa no meu estado interno”, mas não pode garantir que “o processo de negócios está correto”.

9. Uma questão de julgamento mais prática

Se você está hesitando se um objeto deve ser projetado como um Ator, sugiro que você não pergunte primeiro “Será que o thread não é seguro”, mas primeiro pergunte:

  1. Possui um estado mutável compartilhado importante dentro dele?
  2. Este estado será acessado por múltiplas tarefas ao mesmo tempo?
  3. Quero que o mundo exterior interaja com ele apenas através de interfaces controladas?
  4. Se o limite de isolamento não for adicionado, será fácil para a equipe utilizá-lo indevidamente no futuro?

Se a resposta para a maioria dessas perguntas for “sim”, então provavelmente é uma boa opção para o Ator.

10. Conclusão: O valor real do Ator é alterar o estado mutável compartilhado de exposição padrão para isolamento padrão.

Para resumir, eu diria:

Actor O que realmente mudou é que “o estado mutável compartilhado finalmente não está mais exposto a todos por padrão”.

O pensamento tradicional é mais parecido com: primeiro compartilhe e depois proteja. A ideia do Ator é: isolar primeiro e depois decidir como acessar.

Em sistemas simultâneos complexos, esta mudança de pensamento é muitas vezes mais importante do que “mais uma ferramenta de segurança de thread”.