Que lições você aprendeu de um projeto que quase / realmente falhou devido a um multithreading ruim?
Às vezes, a estrutura impõe um certo modelo de encadeamento que torna as coisas em uma ordem de magnitude mais difíceis de acertar.
Quanto a mim, ainda estou para me recuperar da última falha e sinto que é melhor não trabalhar em nada que tenha a ver com multithreading nesse quadro.
Eu descobri que era bom em problemas de multithreading que possuem simples junção / forquilha e onde os dados trafegam apenas em uma direção (enquanto os sinais podem trafegar em uma direção circular).
Não consigo lidar com a GUI na qual algum trabalho pode ser feito apenas em um thread estritamente serializado (o "thread principal") e outro trabalho só pode ser feito em qualquer thread, exceto o thread principal (os "threads de trabalho") e onde dados e mensagens precisam viajar em todas as direções entre N componentes (um gráfico totalmente conectado).
Na época em que deixei esse projeto para outro, havia problemas de impasse por toda parte. Ouvi dizer que, 2-3 meses depois, vários outros desenvolvedores conseguiram corrigir todos os problemas de conflito, a ponto de poderem ser enviados aos clientes. Eu nunca consegui descobrir o conhecimento que faltava.
Algo sobre o projeto: o número de IDs de mensagens (valores inteiros que descrevem o significado de um evento que pode ser enviado para a fila de mensagens de outro objeto, independentemente do encadeamento), chega a vários milhares. Seqüências de caracteres únicas (mensagens de usuário) também chegam a cerca de mil.
Adicionado
A melhor analogia que recebi de outra equipe (não relacionada aos meus projetos passados ou presentes) foi "colocar os dados em um banco de dados". ("Banco de dados" referente à centralização e atualizações atômicas.) Em uma GUI fragmentada em várias visualizações, todas em execução no mesmo "encadeamento principal" e todo o levantamento pesado não-GUI é feito em encadeamentos de trabalho individuais, os dados do aplicativo devem seja armazenado em um único local que atue como um banco de dados e deixe o "banco de dados" lidar com todas as "atualizações atômicas" que envolvem dependências de dados não triviais. Todas as outras partes da GUI lidam apenas com o desenho da tela e nada mais. As partes da interface do usuário podem armazenar em cache coisas e o usuário não notará se estiver obsoleto por uma fração de segundo, se for projetado corretamente. Esse "banco de dados" também é conhecido como "o documento" na arquitetura Document-View. Infelizmente, não, meu aplicativo realmente armazena todos os dados nas Views. Não sei por que foi assim.
Colaboradores:
(os colaboradores não precisam usar exemplos reais / pessoais. As lições de exemplos anedóticos, se você julgar credível, também serão bem-vindas.)
fonte
Respostas:
Minha lição favorita - ganhou muito! - é que em um programa multithread o agendador é um porco furtivo que o odeia. Se as coisas derem errado, eles vão, mas de uma maneira inesperada. Cometa algo errado e você estará perseguindo erros estranhos de heisen (porque qualquer instrumentação que você adicionar alterará os tempos e fornecerá um padrão de execução diferente).
A única maneira sensata de corrigir isso é restringir rigorosamente todo o manuseio de encadeamentos em um pequeno pedaço de código que funcione corretamente e que seja muito conservador quanto à garantia de que os bloqueios sejam mantidos adequadamente (e com uma ordem de aquisição globalmente constante também) . A maneira mais fácil de fazer isso é não compartilhar memória (ou outros recursos) entre threads, exceto para mensagens que devem ser assíncronas; que permite que você escreva todo o resto em um estilo que não reconhece os threads. (Bônus: expandir para várias máquinas em um cluster é muito mais fácil.)
fonte
is that in a multithreaded program the scheduler is a sneaky swine that hates you.
- não, não, ele faz exatamente o que você disse para fazer :)Aqui estão algumas lições básicas em que posso pensar agora (não em projetos com falha, mas com problemas reais vistos em projetos reais):
fonte
Herdamos uma parte em que o projeto GUI está usando uma dúzia de threads. Está dando nada além de problemas. Impasses, problemas de corrida, chamadas GUI de discussão cruzada ...
fonte
O Java 5 e posteriores têm Executors que visam facilitar a vida no manuseio de programas no estilo de junção de forquilha com vários threads.
Use-os, isso removerá muita dor.
(e, sim, isso eu aprendi com um projeto :))
fonte
Tenho experiência em sistemas embarcados em tempo real. Você não pode testar a ausência de problemas causados pelo multithreading. (Às vezes você pode confirmar a presença). O código deve estar comprovadamente correto. Portanto, práticas recomendadas para toda e qualquer interação de encadeamento.
fonte
Uma analogia de uma aula sobre multithreading que fiz no ano passado foi muito útil. A sincronização de threads é como um sinal de tráfego que protege uma interseção (dados) de ser usada por dois carros (threads) de uma só vez. O erro que muitos desenvolvedores cometem é acender luzes vermelhas na maior parte da cidade para deixar um carro passar, porque eles acham que é muito difícil ou perigoso descobrir o sinal exato de que precisam. Isso pode funcionar bem quando o tráfego é baixo, mas leva a um impasse à medida que o aplicativo cresce.
Isso já era algo que eu sabia na teoria, mas depois dessa aula a analogia realmente ficou comigo, e fiquei espantado com a frequência com que depois investigava um problema de encadeamento e encontrava uma fila gigante, ou interrompia a desativação de todos os lugares durante a gravação em uma variável apenas dois encadeamentos utilizados ou mutexes foram mantidos por um longo período em que poderiam ser refatorados para evitá-lo por completo.
Em outras palavras, alguns dos piores problemas de encadeamento são causados por um exagero na tentativa de evitar problemas de encadeamento.
fonte
Tente fazer isso de novo.
Pelo menos para mim, o que criou uma diferença foi a prática. Depois de fazer o trabalho multiencadeado e distribuído algumas vezes, você pega o jeito.
Eu acho que a depuração é realmente o que dificulta. Posso depurar o código multiencadeado usando o VS, mas estou realmente perdido se precisar usar o gdb. A culpa é minha, provavelmente.
Outra coisa que está aprendendo mais sobre é bloquear estruturas de dados livres.
Eu acho que essa pergunta pode ser realmente melhorada se você especificar a estrutura. Pools de threads .NET e trabalhadores em segundo plano são realmente diferentes do QThread, por exemplo. Sempre há algumas dicas específicas da plataforma.
fonte
Aprendi que retornos de chamada de módulos de nível inferior para módulos de nível superior são um grande mal, porque causam a aquisição de bloqueios em uma ordem oposta.
fonte