Simultaneidade: como você aborda o design e depura a implementação?


37

Estou desenvolvendo sistemas concorrentes há vários anos e tenho uma boa compreensão do assunto, apesar da minha falta de treinamento formal (ou seja, sem diploma). Existem alguns idiomas novos que se tornaram populares, pelo menos ultimamente, criados para facilitar a concorrência, como Erlang e Go. Parece que a abordagem deles à concorrência reflete minha própria experiência de como tornar os sistemas escaláveis ​​e tirar proveito de vários núcleos / processadores / máquinas.

No entanto, acho que existem muito poucas ferramentas para ajudar a visualizar o que você pretende fazer e verificar se você está pelo menos próximo da sua visão original. Depurar código simultâneo pode ser um pesadelo com linguagens que não foram projetadas para simultaneidade (como C / C ++, C #, Java, etc.). Em particular, pode ser quase impossível recriar condições que ocorrem prontamente em um sistema em seu ambiente de desenvolvimento.

Então, quais são suas abordagens para projetar um sistema para lidar com simultaneidade e processamento paralelo? Exemplos:

  • Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?
  • Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?
  • Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Eu tenho minhas próprias respostas para algumas delas, mas também gostaria de aprender um pouco mais.

Editar

Até agora, temos muitas informações boas. Muitos dos artigos vinculados são muito bons, e eu já li alguns deles.

Minha experiência pessoal com programação simultânea me leva a acreditar que você precisa de uma mentalidade diferente da necessária com a programação seqüencial. A divisão mental é provavelmente tão ampla quanto a diferença entre programação orientada a objetos e programação procedural. Eu gostaria que esse conjunto de perguntas se concentrasse mais nos processos de pensamento necessários (isto é, na teoria) para abordar sistematicamente as respostas. Ao fornecer respostas mais concretas, ajuda a dar um exemplo - algo pelo qual você passou pessoalmente.

Objetivo da recompensa

Não me diga o que devo fazer. Eu já tenho isso sob controle. Me diga o que voce faz. Diga-me como você resolve esses problemas.


Essa é uma boa pergunta - muita profundidade possível. Eu também obtive uma boa experiência com aplicativos multithread em Java, mas gostaria de aprender mais.
Michael K

Até agora, temos algumas boas respostas. Alguém quer arriscar uma facada no que você gostaria de ter para ajudá-lo nesta área?
Berin Loritsch

TotalView Debugger para codificação simultânea é uma ferramenta bastante útil, leva um pouco de uma curva de aprendizagem embora - totalviewtech.com/products/totalview.html
Fanatic23

Talvez o registro possa ajudá-lo com duas últimas perguntas.
Amir Rezaei

O que estou procurando são os processos das pessoas. São áreas em que as ferramentas que uso são inadequadas, mas podem fazer o trabalho. Estou menos preocupado em citar o artigo de outra pessoa e mais preocupado com a metodologia aqui.
Berin Loritsch

Respostas:


11

Estou desenvolvendo sistemas concorrentes há vários anos e tenho uma boa compreensão do assunto, apesar da minha falta de treinamento formal (ou seja, sem diploma).

Muitos dos melhores programadores que conheço não terminaram a Universidade. Quanto a mim, estudei filosofia.

C / C ++, C #, Java etc.). Em particular, pode ser quase impossível recriar condições que ocorrem prontamente em um sistema em seu ambiente de desenvolvimento.

sim

Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?

geralmente começamos com uma metáfora de 1000 milhas de altura para esclarecer nossa arquitetura para nós mesmos (em primeiro lugar) e para os outros (segundo).

Quando enfrentamos esse problema, sempre encontramos uma maneira de limitar a visibilidade de objetos concorrentes a objetos não concorrentes.

Ultimamente, descobri atores em scala e vi que minhas soluções antigas eram uma espécie de "miniactors", muito menos poderosas que as de scala. Então, minha sugestão é começar a partir daí.

Outra sugestão é pular o maior número possível de problemas: por exemplo, usamos cache centralizado (terracota) em vez de manter mapas na memória, usar retornos de chamada de classe interna em vez de métodos sincronizados, enviar mensagens em vez de escrever memória compartilhada etc.

Com o scala, é tudo muito mais fácil de qualquer maneira.

Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Nenhuma resposta real aqui. Temos alguns testes de unidade para simultaneidade e temos um conjunto de testes de carga para enfatizar o aplicativo o máximo possível.

Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Novamente, não há resposta real: projetamos nossa metáfora no quadro branco e tentamos garantir que não haja conflitos no lado da arquitetura.

Para Arch, quero dizer a definição de Neal Ford: Sw Architecture é tudo o que será muito difícil mudar mais tarde.

a programação me leva a acreditar que você precisa de uma mentalidade diferente da da programação seqüencial.

Talvez, mas para mim seja simplesmente impossível pensar de maneira paralela, é melhor projetar nosso software de uma maneira que não exija pensamento paralelo e com grades de proteção claras para evitar falhas entre as faixas de concorrência.


6

Para mim é tudo sobre os dados. Quebre seus dados corretamente e o processamento paralelo é fácil. Todos os problemas com retenção, impasses e assim desaparecem.

Eu sei que essa não é a única maneira de paralelizar, mas para mim é a mais útil.

Para ilustrar, uma história (não tão rápida):

Trabalhei em um grande sistema financeiro (controle do mercado de ações) de 2007 a 2009, e o volume de processamento dos dados foi muito grande. Para ilustrar, todos os cálculos feitos em uma única conta de um cliente levaram cerca de 1 a 3 segundos em sua estação de trabalho média e havia mais de 30 mil contas. Todas as noites, fechar o sistema era uma grande dor para os usuários (geralmente mais de 6 horas de processamento, sem margem de erro para eles).

O estudo do problema revelou ainda que poderíamos paralelizar os cálculos entre vários computadores, mas ainda teríamos um grande gargalo no antigo servidor de banco de dados (um servidor SQL 2000 emulando o SQL 6.5).

Ficou bem claro que nosso pacote mínimo de processamento era o cálculo de uma única conta, e o principal gargalo era a retenção do servidor de banco de dados (pudemos ver nas várias conexões do "sp_who" esperando o mesmo processamento). Portanto, o processo paralelo foi assim:

1) Um único produtor, responsável por ler o banco de dados ou escrever nele, sequencialmente. Nenhuma simultaneidade permitida aqui. O produtor preparou uma fila de empregos para os consumidores. O banco de dados pertencia apenas a esse produtor.

2) Vários consumidores, em várias máquinas. Cada um dos consumidores recebeu um pacote inteiro de dados, da fila, pronto para o cálculo. Cada operação deqeue é sincronizada.

3) Após o cálculo, cada consumidor retornou os dados para uma fila sincronizada na memória para o produtor, a fim de persistir os dados.

Havia vários pontos de verificação, vários mecanismos para garantir que as transações fossem salvas corretamente (nenhuma foi deixada para trás), mas todo o trabalho valeu a pena. No final, os cálculos distribuídos entre 10 computadores (mais o computador produtor / fila) reduziram o tempo de fechamento de todo o sistema para 15 minutos.

Acabar com os problemas de retenção causados ​​pelo mau gerenciamento de simultaneidade do SQL 6.5 nos deu uma grande vantagem. O resto era praticamente linear, cada novo computador adicionado à "grade" reduzia o tempo de processamento, até atingirmos a "eficiência máxima" das operações seqüenciais de leitura / gravação no banco de dados.


2

Trabalhar em um ambiente com vários threads é difícil e precisa da disciplina de codificação. Você precisa seguir as diretrizes apropriadas para bloquear, liberar bloqueio, acessar variáveis ​​globais etc.

Deixe-me tentar responder à sua pergunta um adeus

* How do you figure out what can be made concurrent vs. what has to be sequential?

Use simultaneidade para

1) Pesquisa: - precisa de um tópico para pesquisar continuamente algo ou enviar a atualização regularmente. (Conceitos como heart-bits, que enviam alguns dados em intervalos regulares ao servidor central para dizer que estou vivo.)

2) As operações com E / S pesadas podem ser feitas paralelamente. O melhor exemplo é o logger. O encadeamento do criador de logs pode ser um encadeamento separado.

3) Tarefas similares em dados diferentes. Se houver alguma tarefa que ocorra em dados diferentes, mas de natureza muito semelhante, threads diferentes poderão fazer isso. O melhor exemplo serão solicitações de servidor.

E, claro, muitos outros assim, dependendo da aplicação.

* How do you reproduce error conditions and view what is happening as the application executes?

Usando logs e impressões de depuração nos logs. Tente registrar também o ID do thread para poder ver o que está acontecendo em cada thread.
Uma maneira de produzir uma condição de erro é colocar o atraso deliberado (no código de depuração) nos locais em que você acha que o problema está acontecendo e interromper forçosamente esse segmento. Coisas semelhantes também podem ser feitas nos depuradores, mas ainda não o fiz.

* How do you visualize the interactions between the different concurrent parts of the application?

Coloque os logs em seus bloqueios, para que você saiba quem está bloqueando o que e quando e quem tentou bloquear. Como eu disse anteriormente, tente colocar a identificação do segmento no log para entender o que está acontecendo em cada segmento.

Este é apenas meu conselho, que é de cerca de 3 anos trabalhando em aplicativos multithread, e espero que ajude.


2
  • Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?

Gostaria de questionar primeiro se o aplicativo (ou componente) realmente beneficiará do processamento simultâneo ou em termos leigos - onde está o gargalo? Obviamente, a concorrência nem sempre oferece benefícios para o investimento necessário para fazê-lo funcionar. Se parecer um candidato, eu trabalharia de baixo para cima - tentando encontrar a maior operação ou conjunto de operações que pode fazer seu trabalho efetivamente de forma isolada - não quero criar threads por insignificantes e ineficazes em termos de custo operações - estou procurando atores .

Trabalhando com Erlang, adorei absolutamente o conceito de usar a passagem assíncrona de mensagens e o modelo de ator para simultaneidade - é intuitivo, eficaz e limpo.

Fora da Concorrência Ator Entendimento

O modelo do ator consiste em alguns princípios fundamentais:

  • Nenhum estado compartilhado
  • Processos leves
  • Passagem de mensagens assíncrona
  • Caixas de correio para armazenar em buffer as mensagens recebidas
  • Processamento de caixa de correio com correspondência de padrões

Um ator é um processo que executa uma função. Aqui, um processo é um encadeamento leve do espaço do usuário (não deve ser confundido com um processo típico do sistema operacional pesado). Os atores nunca compartilham estado e, portanto, nunca precisam competir por bloqueios para acessar dados compartilhados. Em vez disso, os atores compartilham dados enviando mensagens imutáveis. Dados imutáveis ​​não podem ser modificados, portanto, as leituras não requerem um bloqueio.

O modelo de simultaneidade Erlang é mais fácil de entender e depurar do que bloquear e compartilhar dados. A maneira como sua lógica é isolada facilita a realização de testes de componentes, transmitindo-lhes mensagens.

Trabalhando com sistemas concorrentes, é assim que meu design funcionou de qualquer maneira em qualquer idioma - uma fila na qual vários threads extraíam dados, executavam uma operação simples e repetiam ou retornavam à fila. Erlang está apenas impondo estruturas de dados imutáveis ​​para evitar efeitos colaterais e reduzir o custo e a complexidade da criação de novos threads.

Esse modelo não é exclusivo da Erlang, mesmo no mundo Java e .NET existem maneiras de criar isso - eu examinaria o CCR (Concurrency and Coordination Runtime) e o Relang (também existe o Jetlang para Java).

  • Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Na minha experiência, a única coisa que posso fazer é me comprometer a rastrear / registrar tudo. Todo processo / encadeamento precisa ter um identificador e cada nova unidade de trabalho precisa ter um ID de correlação. Você precisa examinar seus logs e rastrear exatamente o que estava sendo processado e quando - não há mágica que eu tenha visto para eliminar isso.

  • Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Veja acima, é feio, mas funciona. A única outra coisa que faço é usar diagramas de sequência UML - é claro que isso ocorre durante o tempo de design - mas você pode usá-los para verificar se seus componentes estão falando da maneira que você deseja.


1

- Minhas respostas são específicas do MS / Visual Studio -

Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?

Isso exigirá conhecimento de domínio, não haverá nenhuma declaração geral aqui para cobri-lo.

Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Muita criação de log, podendo ativar / desativar / ativar o log em aplicativos de produção para capturá-lo na produção. O VS2010 Intellitrace deve ser capaz de ajudar com isso, mas ainda não o usei.

Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Não tenho uma boa resposta para isso, gostaria de ver uma.


O registro mudará a forma como o código é executado e, portanto, pode levar ao erro que você está após não aparecer.
Matthew Leia

1

Não concordo com sua afirmação de que C não foi projetado para simultaneidade. O C é projetado para programação geral de sistemas e possui uma tenacidade para apontar decisões críticas a serem tomadas, e continuará a fazê-lo nos próximos anos. Isso é verdade mesmo quando a melhor decisão pode ser não usar C. Além disso, a simultaneidade em C é tão difícil quanto seu design é complexo.

Tento, da melhor maneira possível, implementar bloqueios com a idéia de que, eventualmente, uma programação verdadeiramente prática e sem bloqueios possa se tornar uma realidade para mim. Ao bloquear, não me refiro à exclusão mútua, apenas a um processo que implementa simultaneidade segura sem a necessidade de arbitragem. Na prática, quero dizer algo que é mais fácil de portar do que implementar. Também tenho muito pouco treinamento formal em CS, mas suponho que tenho permissão para desejar :)

Depois disso, a maioria dos bugs que encontro tornam-se relativamente rasos, ou tão completamente incompreensíveis, que me retiro para um pub. O pub se torna uma opção atraente apenas quando a criação de perfil de um programa diminui o suficiente para expor outras raças que não estão relacionadas ao que estou tentando encontrar.

Como outros já apontaram, o problema que você descreve é ​​extremamente específico do domínio. Eu apenas tento, com o melhor de minha capacidade, evitar qualquer caso que possa exigir arbitragem (fora do meu processo) sempre que possível. Se isso parece uma dor real, reavaliamos a opção de conceder a vários threads ou processos acesso simultâneo e não serializado a algo.

Então, novamente, jogue 'distribuído' lá e a arbitragem se torna uma obrigação. Você tem um exemplo específico?


Para esclarecer minha afirmação, C não foi projetado especificamente para e em torno da simultaneidade. Isso contrasta com idiomas como Go, Erlang e Scala, que foram projetados explicitamente com a simultaneidade em mente. Eu não tinha a intenção de dizer que você não pode fazer concorrência com C.
Berin Loritsch

1

Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Com base na minha experiência, a resposta para esses dois aspectos é a seguinte:

Rastreio Distribuído

O rastreamento distribuído é uma tecnologia que captura dados de tempo para cada componente simultâneo individual do seu sistema e os apresenta em formato gráfico. Representações de execuções simultâneas são sempre intercaladas, permitindo que você veja o que está sendo executado em paralelo e o que não é.

O rastreamento distribuído deve sua origem aos sistemas distribuídos (é claro), que são por definição assíncronos e altamente simultâneos. Um sistema distribuído com rastreamento distribuído permite que as pessoas:

a) identifique gargalos importantes, b) obtenha uma representação visual das 'execuções' ideais de sua aplicação, ec) forneça visibilidade sobre o comportamento simultâneo que está sendo executado; d) obtenha dados de tempo que podem ser usados ​​para avaliar as diferenças entre as alterações no sistema (extremamente importante se você tiver SLAs fortes).

As conseqüências do rastreamento distribuído, no entanto, são:

  1. Ele adiciona sobrecarga a todos os seus processos simultâneos, pois se traduz em mais código para executar e enviar potencialmente em uma rede. Em alguns casos, essa sobrecarga é altamente significativa - até o Google usa apenas o sistema de rastreamento Dapper em um pequeno subconjunto de todas as solicitações para não prejudicar a experiência do usuário.

  2. Existem muitas ferramentas diferentes, nem todas interoperáveis ​​entre si. Isso é um pouco melhorado por padrões como o OpenTracing, mas não totalmente resolvido.

  3. Não diz nada sobre recursos compartilhados e seu status atual. Você pode adivinhar, com base no código do aplicativo e no gráfico que está mostrando, mas não é uma ferramenta útil nesse sentido.

  4. As ferramentas atuais assumem que você tem memória e armazenamento de sobra. Hospedar um servidor de séries temporais pode não ser barato, dependendo de suas restrições.

Software de rastreamento de erros

Eu vinculo ao Sentry acima principalmente porque é a ferramenta mais usada por aí, e por boas razões - software de rastreamento de erros como a execução em tempo de execução do Sentry hijack para encaminhar simultaneamente um rastreamento de pilha dos erros encontrados para um servidor central.

O benefício líquido desse software dedicado em código simultâneo:

  1. Erros duplicados não são duplicados . Em outras palavras, se um ou mais sistemas simultâneos encontrarem a mesma exceção, o Sentry aumentará um relatório de incidente, mas não enviará duas cópias do incidente.

Isso significa que você pode descobrir qual sistema simultâneo está enfrentando qual tipo de erro sem precisar passar por inúmeros relatórios de erros simultâneos. Se você já sofreu spam de e-mail de um sistema distribuído, sabe como é o inferno.

Você pode até 'marcar' aspectos diferentes do seu sistema simultâneo (embora isso pressuponha que você não tenha trabalho intercalado em exatamente um encadeamento, o que tecnicamente não é simultâneo de qualquer maneira, já que o encadeamento está simplesmente pulando entre as tarefas com eficiência, mas ainda deve processar os manipuladores de eventos até a conclusão) e veja um detalhamento dos erros por tag.

  1. Você pode modificar esse software de tratamento de erros para fornecer detalhes adicionais com suas exceções de tempo de execução. Que recursos abertos o processo tinha? Existe um recurso compartilhado que este processo estava mantendo? Qual usuário teve esse problema?

Isso, além de rastreamentos meticulosos de pilha (e mapas de origem, se você precisar fornecer uma versão reduzida de seus arquivos), facilita a determinação do que está acontecendo de errado em grande parte do tempo.

  1. (Específico ao Sentry) Você pode ter um painel de relatórios Sentry separado para execuções de teste do sistema, permitindo detectar erros nos testes.

As desvantagens de tal software incluem:

  1. Como tudo, eles adicionam em massa. Você pode não querer esse sistema em hardware incorporado, por exemplo. Eu recomendo fazer uma execução de teste desse software, comparando uma execução simples com e sem a amostragem de algumas centenas de execuções em uma máquina ociosa.

  2. Nem todos os idiomas são igualmente suportados, pois muitos desses sistemas dependem da captura implícita de uma exceção e nem todos os idiomas apresentam exceções robustas. Dito isto, existem clientes para uma grande quantidade de sistemas.

  3. Eles podem ser gerados como um risco de segurança, pois muitos desses sistemas são essencialmente de código fechado. Nesses casos, faça sua devida diligência em pesquisá-los ou, se preferir, faça o seu próprio.

  4. Nem sempre eles podem fornecer as informações necessárias. Este é um risco com todas as tentativas de adicionar visibilidade.

  5. A maioria desses serviços foi projetada para aplicativos da Web altamente simultâneos; portanto, nem todas as ferramentas podem ser perfeitas para o seu caso de uso.

Em resumo : ter visibilidade é a parte mais crucial de qualquer sistema concorrente. Os dois métodos que descrevi acima, em conjunto com painéis dedicados sobre hardware e dados para obter uma imagem sólida do sistema em um determinado momento, são amplamente utilizados em toda a indústria precisamente para abordar esse aspecto.

Algumas sugestões adicionais

Passei mais tempo do que me importo em corrigir o código por pessoas que tentaram resolver problemas simultâneos de maneiras terríveis. Sempre que encontrei casos em que as seguintes coisas poderiam melhorar muito a experiência do desenvolvedor (que é tão importante quanto a experiência do usuário):

  • Confie nos tipos . A digitação existe para validar seu código e pode ser usada em tempo de execução como uma proteção extra. Onde a digitação não existir, conte com asserções e um manipulador de erros adequado para detectar erros. O código simultâneo requer código defensivo e os tipos servem como o melhor tipo de validação disponível.

    • Teste os links entre os componentes do código , não apenas o componente em si. Não confunda isso com um teste de integração completo - que testa todos os links entre todos os componentes e, mesmo assim, ele procura apenas uma validação global do estado final. Esta é uma maneira terrível de detectar erros.

Um bom teste de link verifica se, quando um componente se comunica com outro componente isoladamente , a mensagem recebida e a mensagem enviada são as mesmas que você espera. Se você tiver dois ou mais componentes que dependem de um serviço compartilhado para se comunicar, ative todos eles, faça com que eles troquem mensagens pelo serviço central e verifique se todos estão obtendo o que você espera no final.

A divisão de testes que envolvem muitos componentes em um teste dos próprios componentes e um teste de como cada um dos componentes se comunica também oferecem maior confiança na validade do seu código. Ter um corpo tão rigoroso de testes permite impor contratos entre serviços, bem como detectar erros inesperados que ocorrem quando eles estão em execução ao mesmo tempo.

  • Use os algoritmos certos para validar o estado do seu aplicativo. Estou falando de coisas simples, como quando você tem um processo mestre aguardando que todos os trabalhadores concluam uma tarefa e só quer passar para a próxima etapa se todos os trabalhadores estiverem completos - este é um exemplo de detecção global terminação, para a qual existem metodologias conhecidas como o algoritmo do Safra.

Algumas dessas ferramentas vêm com os idiomas - o Rust, por exemplo, garante que seu código não terá condições de corrida no tempo de compilação, enquanto o Go possui um detector de conflito embutido que também é executado no tempo de compilação. Se você pode detectar problemas antes que eles atinjam a produção, é sempre uma vitória.

Uma regra geral: projetar falhas em sistemas concorrentes . Antecipe que serviços comuns travarão ou quebrarão. Isso vale mesmo para o código que não é distribuído entre as máquinas - o código simultâneo em uma única máquina pode depender de dependências externas (como um arquivo de log compartilhado, um servidor Redis, um maldito servidor MySQL) que podem desaparecer ou ser removidos a qualquer momento .

A melhor maneira de fazer isso é validar o estado do aplicativo de tempos em tempos - faça verificações de integridade para cada serviço e verifique se os consumidores desse serviço são notificados de problemas de saúde. Ferramentas modernas de contêineres como o Docker fazem isso muito bem e devem ser usadas para guardar coisas na área de areia.

Como você descobre o que pode ser tornado simultâneo e o que pode ser sequencial?

Uma das maiores lições que aprendi trabalhando em um sistema altamente simultâneo é esta: você nunca pode ter métricas suficientes . As métricas devem conduzir absolutamente tudo em seu aplicativo - você não é um engenheiro se não estiver medindo tudo.

Sem métricas, você não pode fazer algumas coisas muito importantes:

  1. Avalie a diferença feita pelas mudanças no sistema. Se você não souber se o botão de ajuste A fez a métrica B subir e a métrica C cair, você não sabe como consertar seu sistema quando as pessoas enviam códigos inesperadamente malignos no sistema (e eles enviam o código para o sistema) .

  2. Entenda o que você precisa fazer a seguir para melhorar as coisas. Até você saber que os aplicativos estão com pouca memória, não é possível discernir se deve obter mais memória ou comprar mais disco para seus servidores.

As métricas são tão cruciais e essenciais que fiz um esforço consciente para planejar o que quero medir antes mesmo de pensar no que um sistema exigirá. De fato, as métricas são tão cruciais que acredito que sejam a resposta certa para essa pergunta: você só sabe o que pode ser sequencial ou simultâneo quando mede o que os bits do seu programa estão fazendo. O design adequado usa números, não suposições.

Dito isto, certamente existem algumas regras práticas:

  1. Sequencial implica dependência. Dois processos devem ser seqüenciais se um for dependente do outro de alguma maneira. Processos sem dependências devem ser simultâneos. No entanto, planeje uma maneira de lidar com falhas no fluxo que não impeça os processos a jusante de esperar indefinidamente.

  2. Nunca misture uma tarefa vinculada de E / S com uma tarefa vinculada à CPU no mesmo núcleo. Não escreva (por exemplo) um rastreador da Web que ative dez solicitações simultâneas no mesmo encadeamento, raspe-as assim que elas chegarem e espere escalar para quinhentas - as solicitações de E / S vão para uma fila em paralelo, mas a CPU ainda passará por eles em série. (Esse modelo orientado a eventos de thread único é popular, mas é limitado por causa desse aspecto - em vez de entender isso, as pessoas simplesmente torcem as mãos e dizem que o Node não escala, para dar um exemplo).

Um único encadeamento pode fazer muito trabalho de E / S. Mas, para usar totalmente a simultaneidade do seu hardware, use conjuntos de threads que juntos ocupam todos os núcleos. No exemplo acima, o lançamento de cinco processos Python (cada um dos quais pode usar um núcleo em uma máquina de seis núcleos) apenas para o trabalho da CPU e um sexto encadeamento Python apenas para o trabalho de E / S serão dimensionados muito mais rapidamente do que você pensa.

A única maneira de tirar proveito da simultaneidade da CPU é através de um conjunto de threads dedicado. Um único encadeamento geralmente é bom o suficiente para muito trabalho vinculado de E / S. É por isso que os servidores Web orientados a eventos, como o Nginx, são dimensionados melhor (eles funcionam puramente com E / S) que o Apache (que confunde o trabalho com E / S com algo que requer CPU e inicia um processo por solicitação), mas por que usar o Node para executar dezenas de milhares de cálculos de GPU recebidos em paralelo é uma péssima ideia.


0

Bem, para o processo de verificação, ao projetar um grande sistema simultâneo - costumo testar o modelo usando o LTSA - Labeled Transition System Analyzer . Foi desenvolvido pelo meu antigo tutor, que é um veterano no campo da concorrência e agora é chefe de computação da Imperial.

Quanto a descobrir o que pode e o que não pode ser simultâneo, existem analisadores estáticos que podem mostrar isso, acredito, embora eu tenha a tendência de desenhar apenas diagramas de agendamento para seções críticas, da mesma forma que faria para o gerenciamento de projetos. Em seguida, identifique as seções que executam a mesma operação repetidamente. Uma rota rápida é apenas para encontrar loops, pois eles tendem a ser as áreas que se beneficiam do processamento paralelo.


0

Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?

Praticamente tudo o que você escreve pode tirar proveito da simultaneidade, especialmente o caso de uso "divida uma conquista". Uma pergunta muito melhor é o que deve ser simultâneo?

Threading em C # de Joseph Albahari lista cinco usos comuns.

Multithreading tem muitos usos; aqui estão os mais comuns:

Mantendo uma interface de usuário responsiva

Ao executar tarefas demoradas em um encadeamento paralelo de "trabalhador", o encadeamento principal da interface do usuário fica livre para continuar processando eventos de teclado e mouse.

Fazendo uso eficiente de uma CPU bloqueada

O multithreading é útil quando um thread aguarda uma resposta de outro computador ou peça de hardware. Enquanto um thread é bloqueado durante a execução da tarefa, outros threads podem tirar proveito do computador sem carga.

Programação paralela

O código que executa cálculos intensivos pode ser executado mais rapidamente em computadores com vários núcleos ou multiprocessadores se a carga de trabalho for compartilhada entre vários threads em uma estratégia de "dividir e conquistar" (consulte a Parte 5).

Execução especulativa

Em máquinas multicore, às vezes você pode melhorar o desempenho prevendo algo que talvez precise ser feito e, em seguida, com antecedência. O LINQPad usa essa técnica para acelerar a criação de novas consultas. Uma variação é executar vários algoritmos diferentes em paralelo que resolvem a mesma tarefa. Qualquer um que termine primeiro "ganha" - isso é eficaz quando você não pode saber antecipadamente qual algoritmo será executado mais rapidamente.

Permitindo que solicitações sejam processadas simultaneamente

Em um servidor, as solicitações do cliente podem chegar simultaneamente e, portanto, precisam ser tratadas em paralelo (o .NET Framework cria threads para isso automaticamente se você usar ASP.NET, WCF, Serviços da Web ou Remoting). Isso também pode ser útil em um cliente (por exemplo, lidar com redes ponto a ponto - ou mesmo várias solicitações do usuário).

Se você não está tentando fazer uma das opções acima, é melhor pensar muito sobre isso.

Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Se você estiver usando .NET e tiver escrito casos de uso, poderá usar o CHESS, que pode recriar condições específicas de intercalação de encadeamentos, o que permite testar sua correção.

Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Depende do contexto. Para os cenários de trabalho, penso em um gerente subordinado. O gerente diz ao subordinado para fazer alguma coisa e aguarda atualizações de status.

Para tarefas simultâneas não relacionadas, penso em elevadores ou carros em faixas de tráfego separadas.

Para sincronização, às vezes penso em semáforos ou estilos de curva.

Além disso, se você estiver usando o C # 4.0, poderá dar uma olhada na Biblioteca Paralela de Tarefas


0

Minha resposta para essas perguntas é:

  • Como você descobre o que pode ser tornado simultâneo versus o que deve ser sequencial?

Primeiro, preciso saber por que devo usar a simultaneidade, porque descobri que as pessoas ficam entusiasmadas com a ideia por trás da simultaneidade, mas nem sempre pensam no problema que estão tentando resolver.

Se você precisar simular uma situação da vida real, como filas, fluxos de trabalho, etc., provavelmente precisará usar uma abordagem simultânea.

Agora que eu sei que devo usá-lo, é hora de analisar o trade-off, se você tiver muitos processos, poderá pensar em sobrecarga de comunicação, mas se precisar de um novo, pode acabar sem solução simultânea (reanalise o problema se então.)

  • Como você reproduz condições de erro e vê o que está acontecendo enquanto o aplicativo é executado?

Não sou especialista neste assunto, mas acho que, para sistemas concorrentes, essa não é a abordagem correta. Uma abordagem teórica deve ser escolhida, procurando os 4 requisitos de impasse em áreas críticas:

  1. Não preferência
  2. Espere e espere
  3. Exclusão motual
  4. Corrente circular

    • Como você visualiza as interações entre as diferentes partes simultâneas do aplicativo?

Tento primeiro identificar quem são os participantes das interações, depois como eles se comunicam e com quem. Finalmente, gráficos e diagramas de interação me ajudam a visualizar. Meu bom e velho quadro branco não pode ser derrotado por nenhum outro tipo de mídia.


0

Eu vou ser franco. Eu adoro ferramentas. Eu uso muitas ferramentas. Meu primeiro passo é traçar os caminhos pretendidos para o fluxo de estado. Meu próximo passo é tentar descobrir se vale a pena ou se o fluxo de informações exigido renderizará o código serial com muita frequência. Então, tentarei esboçar alguns modelos simples. Isso pode variar de uma pilha de esculturas de palitos de dente a alguns exemplos semelhantes simples em python. Em seguida, analiso alguns dos meus livros favoritos, como o livrinho de semáforos, e vejo se alguém já encontrou uma solução melhor para o meu problema.

Então eu começo a codificar.
Só brincando. Um pouco mais de pesquisa primeiro. Eu gosto de me sentar com um colega hacker e acompanhar a execução esperada do programa em alto nível. Se surgirem perguntas, passamos para um nível inferior. É importante descobrir se alguém pode entender sua solução o suficiente para mantê-la.

Finalmente, começo a codificar. Tento mantê-lo muito simples primeiro. Apenas o caminho do código, nada extravagante. Mova o mínimo de estado possível. Evite gravações. Evite leituras que possam entrar em conflito com gravações. Evite, acima de tudo, gravações que possam entrar em conflito com gravações. É muito fácil descobrir que você possui um número positivamente tóxico e que sua bela solução é de repente pouco mais do que uma abordagem serial que debulha em cache.

Uma boa regra é usar estruturas sempre que possível. Se você mesmo estiver escrevendo componentes básicos de encadeamento, como boas estruturas de dados sincronizados ou proibições a Deus, sincronizadas e primitivas, você quase certamente vai explodir sua perna inteira.

Finalmente, ferramentas. A depuração é muito difícil. Uso valgrind \ callgrind no linux em conjunto com PIN e estúdios paralelos no windows. Não tente depurar esse material manualmente. Você provavelmente pode. Mas você provavelmente desejaria não ter. Dez horas dominando algumas ferramentas poderosas e alguns bons modelos economizarão centenas de horas depois.

Acima de tudo, trabalhe de forma incremental. Trabalhe com cuidado. Não escreva código simultâneo quando estiver cansado. Não escreva enquanto estiver com fome. De fato, se você puder evitá-lo, simplesmente não o escreva. A simultaneidade é difícil, e descobri que muitos aplicativos que o listam como um recurso geralmente são enviados como único recurso.

Em resumo:
Begin:
Pense em
Talk
Test
Write simplesmente
Leia
Test
Write
Debug
GOTO Begin

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.