Uma introdução suave ao multithreading.
Uma breve discussão sobre tecnologia multi-threading
Uma introdução suave ao multithreading
Uma breve discussão sobre tecnologia multi-threading
Abordando o mundo da simultaneidade, um passo de cada vez. Aborde o mundo da simultaneidade passo a passo.
Os computadores modernos têm a capacidade de realizar várias operações ao mesmo tempo. Apoiado por avanços de hardware e sistemas operacionais mais inteligentes, esse recurso faz com que seus programas sejam executados mais rapidamente, tanto em termos de velocidade de execução quanto de capacidade de resposta. Os computadores modernos têm a capacidade de realizar múltiplas operações simultaneamente. Alimentado por melhorias de hardware e um sistema operacional inteligente, esse recurso faz com que os programas sejam executados mais rapidamente, tanto em termos de velocidade de execução quanto de capacidade de resposta.
Escrever software que aproveite esse poder é fascinante, mas complicado: exige que você entenda o que acontece nos bastidores do seu computador. Neste primeiro episódio tentarei arranhar a superfície dos threads, uma das ferramentas fornecidas pelos sistemas operacionais para realizar esse tipo de mágica. Vamos! Escrever software que aproveite esse poder é fascinante, mas também complicado: exige que você entenda o que está acontecendo nos bastidores do seu computador. Neste artigo, tentarei arranhar a superfície dos threads, ferramenta fornecida pelo sistema operacional para realizar essa função mágica. Vamos começar!
Processos e threads: nomeando as coisas da maneira certa
Processos e Threads: Nomeie-os da maneira certa
Os sistemas operacionais modernos podem executar vários programas ao mesmo tempo. É por isso que você pode ler este artigo no seu navegador (um programa) enquanto ouve música no seu media player (outro programa). Cada programa é conhecido como um processo que está sendo executado. O sistema operacional conhece muitos truques de software para fazer um processo funcionar junto com outros, além de aproveitar as vantagens do hardware subjacente. De qualquer forma, o resultado final é que você sente que todos os seus programas estão sendo executados simultaneamente. Os sistemas operacionais modernos podem executar vários programas simultaneamente. É por isso que você pode ler este artigo no seu navegador (um programa) enquanto ouve música no seu media player (outro programa). Todo programa é chamado de processo em execução. O sistema operacional conhece muitos truques de software para fazer com que os processos sejam executados junto com outros processos e para aproveitar as vantagens do hardware subjacente. De qualquer forma, o resultado final é que você sente que todos os seus programas estão sendo executados simultaneamente.
A execução de processos em um sistema operacional não é a única maneira de realizar diversas operações ao mesmo tempo. Cada processo é capaz de executar subtarefas simultâneas dentro de si, chamadas threads. Você pode pensar em um thread como uma fatia do próprio processo. Cada processo aciona pelo menos um thread na inicialização, que é chamado de thread principal. Então, de acordo com as necessidades do programa/programador, threads adicionais podem ser iniciados ou finalizados. Multithreading consiste na execução de vários threads em um único processo. A execução de processos em um sistema operacional não é a única maneira de realizar várias operações simultaneamente. Cada processo é capaz de executar subtarefas simultaneamente dentro de si, chamadas threads. Você pode pensar nos threads como parte do próprio processo. Cada processo aciona pelo menos um thread quando é iniciado, chamado de thread principal. Então, dependendo das necessidades do programa/programador, outros threads podem ser iniciados ou encerrados. Multithreading refere-se à execução de vários threads em um processo.
Por exemplo, é provável que seu reprodutor de mídia execute vários threads: um para renderizar a interface — geralmente é o thread principal, outro para reproduzir a música e assim por diante. Por exemplo, seu reprodutor de mídia pode executar vários threads: um para renderizar a interface – geralmente é o thread principal, outro para reproduzir música e assim por diante.
Você pode pensar no sistema operacional como um contêiner que contém vários processos, onde cada processo é um contêiner que contém vários threads. Neste artigo focarei apenas nos tópicos, mas todo o tópico é fascinante e merece uma análise mais aprofundada no futuro. Você pode pensar no sistema operacional como um contêiner com vários processos, onde cada processo é um contêiner com vários threads. Neste artigo focarei apenas nos tópicos, mas o tema geral é fascinante e merece uma análise mais aprofundada no futuro.

- Os sistemas operacionais podem ser vistos como uma caixa que contém processos, que por sua vez contêm um ou mais threads.
As diferenças entre processos e threads
Diferença entre processo e thread
Cada processo possui seu próprio pedaço de memória atribuído pelo sistema operacional. Por padrão, essa memória não pode ser compartilhada com outros processos: seu navegador não tem acesso à memória atribuída ao seu media player e vice-versa. A mesma coisa acontece se você executar duas instâncias do mesmo processo, ou seja, se você iniciar o navegador duas vezes. (IPC). Cada processo possui um bloco de memória alocado pelo sistema operacional. Por padrão, a memória não pode ser compartilhada com outros processos: o navegador não pode acessar a memória alocada para o media player e vice-versa. A mesma coisa acontece se você executar duas instâncias do mesmo processo, ou seja, iniciar o navegador duas vezes. O sistema operacional trata cada instância como um novo processo, alocando sua própria porção independente de memória. Portanto, por padrão, dois ou mais processos não podem compartilhar dados, a menos que executem um truque avançado chamado comunicação entre processos (IPC).Ao contrário dos processos, os threads compartilham o mesmo pedaço de memória atribuído ao seu processo pai pelo sistema operacional: os dados na interface principal do media player podem ser facilmente acessados pelo mecanismo de áudio e vice-versa. Portanto é mais fácil para dois threads se comunicarem. Além disso, os threads costumam ser mais leves que um processo: consomem menos recursos e são mais rápidos de criar, por isso também são chamados de processos leves. Ao contrário dos processos, os threads compartilham o mesmo bloco de memória alocado pelo sistema operacional para o processo pai: os dados na interface principal do reprodutor de mídia podem ser facilmente acessados pelo mecanismo de áudio e vice-versa. Portanto, a conversa entre os dois threads fica mais fácil. Além disso, os threads são geralmente mais leves que os processos: consomem menos recursos e são mais rápidos de criar, por isso também são chamados de processos leves.
Threads são uma maneira prática de fazer seu programa executar várias operações ao mesmo tempo. Sem threads você teria que escrever um programa por tarefa, executá-los como processos e sincronizá-los através do sistema operacional. Isso seria mais difícil (IPC é complicado) e mais lento (os processos são mais pesados que os threads). Threads são uma maneira conveniente para um programa executar várias operações simultaneamente. Sem threads, você teria que escrever um programa para cada tarefa, executá-los como processos e sincronizá-los por meio do sistema operacional. Isso será mais difícil (IPC é mais complicado) e mais lento (os processos são mais pesados que os threads).
Fios verdes, de fibras
Fio de fibra verde
Threads mencionados até agora são uma coisa do sistema operacional: um processo que deseja disparar um novo thread precisa se comunicar com o sistema operacional. Porém, nem todas as plataformas oferecem suporte nativo a threads. Threads verdes, também conhecidos como fibras, são um tipo de emulação que faz programas multithreaded funcionarem em ambientes que não oferecem esse recurso. Por exemplo, uma máquina virtual pode implementar threads verdes caso o sistema operacional subjacente não tenha suporte nativo a threads. Os threads mencionados até agora são todos problemas do sistema operacional: um processo que deseja acionar um novo thread deve se comunicar com o sistema operacional. No entanto, nem todas as plataformas suportam threads. Os threads verdes, também conhecidos como fibras, são uma emulação que permite que programas multithreaded funcionem em ambientes que não oferecem essa funcionalidade. Por exemplo, uma máquina virtual pode implementar threads verdes caso o sistema operacional subjacente não suporte threads nativos.
Threads verdes são mais rápidos de criar e gerenciar porque ignoram completamente o sistema operacional, mas também apresentam desvantagens. Escreverei sobre esse assunto em um dos próximos episódios. Threads verdes são mais rápidos de criar e gerenciar porque ignoram completamente o sistema operacional, mas apresentam desvantagens. Escreverei um artigo sobre esse assunto no próximo episódio.
O nome “threads verdes” refere-se à equipe verde da Sun Microsystem que projetou a biblioteca de threads Java original na década de 90. Hoje Java não faz mais uso de threads verdes: eles mudaram para threads nativos em 2000. Algumas outras linguagens de programação – Go, Haskell ou Rust, para citar algumas – implementam equivalentes de threads verdes em vez de nativos. O nome “Green Threads” foi cunhado pela equipe verde da Sun Microsystem, que projetou a biblioteca original de threading Java na década de 1990. Hoje, Java não usa mais threads verdes: eles mudaram para threads nativos em 2000. Algumas outras linguagens de programação - Go, Haskell ou Rust, para citar algumas - implementam equivalentes de threads verdes em vez de threads nativos.
Para que threads são usados
Para que servem os threads?
Por que um processo deveria empregar vários threads? Como mencionei antes, fazer as coisas em paralelo acelera muito as coisas. Digamos que você esteja prestes a renderizar um filme em seu editor de filmes. O editor pode ser inteligente o suficiente para distribuir a operação de renderização por vários threads, onde cada thread processa uma parte do filme final. Portanto, se com um thread a tarefa levaria, digamos, uma hora, com dois threads levaria 30 minutos; com quatro fios 15 minutos e assim por diante. Por que um processo deveria usar vários threads? Como mencionei antes, o processamento paralelo pode acelerar significativamente as coisas. Digamos que você queira renderizar um filme em um editor de filmes. O editor pode ser inteligente o suficiente para distribuir as operações de renderização por vários threads, onde cada thread lida com uma parte do filme final. Portanto, se uma tarefa leva uma hora com um thread, 30 minutos com dois threads; 15 minutos com quatro tópicos e assim por diante.
É realmente tão simples? Existem três pontos importantes a serem considerados: É realmente tão simples? Existem três pontos a considerar:
Primeiro, nem todo programa precisa ser multithread. Se seu aplicativo executa operações sequenciais ou espera frequentemente que o usuário faça algo, o multithreading pode não ser tão benéfico; Nem todo programa precisa de multithreading. Se o seu aplicativo executa operações sequenciais ou espera frequentemente que o usuário execute determinadas ações, o multithreading pode não ser tão benéfico;
-
você simplesmente não lança mais threads em um aplicativo para fazê-lo rodar mais rápido: cada subtarefa deve ser pensada e projetada cuidadosamente para realizar operações paralelas; Você não precisa lançar mais threads no aplicativo para fazê-lo rodar mais rápido: cada subtarefa deve ser cuidadosamente pensada e projetada para executar operações paralelas;
-
não é 100% garantido que os threads realizarão suas operações verdadeiramente em paralelo, ou seja, ao mesmo tempo: isso realmente depende do hardware subjacente. Não há 100% de garantia de que os threads realmente realizarão suas operações em paralelo, ou seja, ao mesmo tempo: isso realmente depende do hardware subjacente.
A última é crucial: se o seu computador não suportar múltiplas operações ao mesmo tempo, o sistema operacional terá que falsificá-las. Veremos como em um minuto. Por enquanto, vamos pensar na simultaneidade como a percepção de ter tarefas que são executadas ao mesmo tempo, enquanto o verdadeiro paralelismo é como tarefas que são literalmente executadas ao mesmo tempo.
Este último ponto é importante: se o seu computador não suportar múltiplas operações ao mesmo tempo, o sistema operacional terá que falsificá-las. Veremos em um momento. Agora, vamos pensar na simultaneidade como a sensação de execução simultânea de tarefas e no verdadeiro paralelismo como tarefas executadas simultaneamente.
2. O paralelismo é um subconjunto da simultaneidade. O paralelismo é um subconjunto da simultaneidade.
O que torna possível a simultaneidade e o paralelismo
O que torna possível a simultaneidade e o paralelismo
A unidade central de processamento (CPU) do seu computador faz o trabalho pesado de executar programas. É composto por várias partes, sendo a principal delas o chamado núcleo: é onde os cálculos são realmente realizados. Um núcleo é capaz de executar apenas uma operação por vez. A unidade central de processamento (CPU) do seu computador é responsável pela execução de programas. É composto por várias partes, sendo a principal delas o chamado núcleo: é aqui que os cálculos são efetivamente realizados. Um núcleo só pode executar uma operação por vez.
É claro que esta é uma grande desvantagem. Por esta razão, os sistemas operacionais desenvolveram técnicas avançadas para dar ao usuário a capacidade de executar vários processos (ou threads) ao mesmo tempo, especialmente em ambientes gráficos, mesmo em uma máquina com um único núcleo. A mais importante é chamada de multitarefa preemptiva, onde preempção é a capacidade de interromper uma tarefa, mudar para outra e retomar a primeira tarefa posteriormente. É claro que esta é uma grande desvantagem. Portanto, os sistemas operacionais desenvolveram técnicas avançadas que permitem aos usuários executar múltiplos processos (ou threads) simultaneamente, especialmente em ambientes gráficos e até mesmo em máquinas de núcleo único. A mais importante delas é chamada de multitarefa preemptiva, onde a preempção é a capacidade de interromper uma tarefa, mudar para outra e então retomar a primeira tarefa posteriormente.
Portanto, se sua CPU tiver apenas um núcleo, parte do trabalho de um sistema operacional é distribuir esse poder de computação de núcleo único por vários processos ou threads, que são executados um após o outro em um loop. Esta operação dá a ilusão de ter mais de um programa rodando em paralelo ou um único programa fazendo várias coisas ao mesmo tempo (se for multithread). A simultaneidade é alcançada, mas o verdadeiro paralelismo — a capacidade de executar processos simultaneamente — ainda está faltando. Portanto, se sua CPU tiver apenas um núcleo, parte do trabalho do sistema operacional é distribuir o poder de computação de um único núcleo por vários processos ou threads que são executados um após o outro em um loop. Esta operação dá a ilusão de que existem vários programas em execução em paralelo ou que um programa está fazendo várias coisas ao mesmo tempo (se for multithread). Embora a simultaneidade seja satisfeita, o verdadeiro paralelismo – a capacidade de executar processos simultaneamente – ainda está faltando.
Hoje, as CPUs modernas têm mais de um núcleo interno, onde cada um executa uma operação independente por vez. Isto significa que com dois ou mais núcleos o verdadeiro paralelismo é possível. Por exemplo, meu Intel Core i7 tem quatro núcleos: ele pode executar quatro processos ou threads diferentes ao mesmo tempo, simultaneamente. Hoje, as CPUs modernas possuem vários núcleos, cada um executando uma operação independente por vez. Isso significa que o verdadeiro paralelismo é possível usando dois ou mais núcleos. Por exemplo, meu Intel Core i7 tem quatro núcleos: ele pode executar quatro processos ou threads diferentes simultaneamente.
Os sistemas operacionais são capazes de detectar o número de núcleos da CPU e atribuir processos ou threads a cada um deles. Um thread pode ser alocado para qualquer núcleo do sistema operacional, e esse tipo de agendamento é completamente transparente para o programa que está sendo executado. Além disso, a multitarefa preemptiva pode entrar em ação caso todos os núcleos estejam ocupados. Isso lhe dá a capacidade de executar mais processos e threads do que o número real ou núcleos disponíveis em sua máquina. O sistema operacional é capaz de detectar o número de núcleos da CPU e atribuir um processo ou thread a cada núcleo da CPU. Um thread pode ser atribuído a qualquer núcleo que o sistema operacional desejar, e esse agendamento é completamente transparente para o programa em execução. Além disso, a multitarefa preemptiva pode entrar em ação quando todos os núcleos estão ocupados. Isso permite que você execute mais processos e threads do que o número real de núcleos disponíveis em seu computador.
Aplicação multi-threading em um único núcleo: faz sentido?
Aplicativos multithread em um único núcleo: faz sentido?
O verdadeiro paralelismo em uma máquina de núcleo único é impossível de ser alcançado. No entanto, ainda faz sentido escrever um programa multithread, se a sua aplicação puder se beneficiar disso. Quando um processo emprega vários threads, a multitarefa preemptiva pode manter o aplicativo em execução mesmo que um desses threads execute uma tarefa lenta ou bloqueadora. É impossível alcançar o verdadeiro paralelismo em uma máquina de núcleo único. No entanto, ainda faz sentido escrever programas multithread se seu aplicativo puder se beneficiar disso. Quando um processo usa vários threads, a multitarefa preemptiva pode manter o aplicativo em execução mesmo se um dos threads executar uma tarefa lenta ou bloqueadora.
Digamos, por exemplo, que você esteja trabalhando em um aplicativo de desktop que lê alguns dados de um disco muito lento. Se você escrever o programa com apenas um thread, todo o aplicativo congelará até que a operação do disco seja concluída: a energia da CPU atribuída ao único thread será desperdiçada enquanto espera o disco ser ativado. É claro que o sistema operacional está executando muitos outros processos além deste, mas seu aplicativo específico não fará nenhum progresso. Por exemplo, você está trabalhando em um aplicativo de desktop que lê alguns dados de um disco muito lento. Se um programa for escrito usando apenas um thread, todo o aplicativo congela até que a operação do disco seja concluída: a energia da CPU alocada para o único thread é desperdiçada enquanto espera o disco ser ativado. É claro que, além desse processo, o sistema operacional possui muitos outros processos em execução, mas seu aplicativo específico não fará nenhum progresso.Vamos repensar seu aplicativo de forma multithread. A thread A é responsável pelo acesso ao disco, enquanto a thread B cuida da interface principal. Se o thread A ficar parado esperando porque o dispositivo está lento, o thread B ainda poderá executar a interface principal, mantendo seu programa responsivo. Isso é possível porque, tendo dois threads, o sistema operacional pode alternar os recursos da CPU entre eles sem ficar preso no mais lento. Vamos repensar seu aplicativo em termos multithread. O thread A é responsável pelo acesso ao disco, enquanto o thread B é responsável pela interface principal. Se o thread A estiver parado esperando porque o dispositivo está lento, o thread B ainda poderá executar a interface principal, mantendo o programa responsivo. Isso é possível porque existem dois threads e o sistema operacional pode alternar recursos da CPU entre eles sem ficar preso no thread mais lento.
Mais tópicos, mais problemas
Quanto mais threads, mais problemas
Como sabemos, threads compartilham o mesmo pedaço de memória de seu processo pai. Isso torna extremamente fácil para dois ou mais deles trocarem dados dentro do mesmo aplicativo. Por exemplo: um editor de filme pode conter uma grande parte da memória compartilhada contendo a linha do tempo do vídeo. Essa memória compartilhada está sendo lida por vários threads de trabalho designados para renderizar o filme em um arquivo. Todos eles só precisam de um identificador (por exemplo, um ponteiro) para essa área de memória para poder lê-la e enviar quadros renderizados para o disco. Sabemos que threads compartilham o mesmo bloco de memória de seu processo pai. Isso torna muito fácil para dois ou mais deles trocarem dados dentro do mesmo aplicativo. Por exemplo: um editor de filme pode possuir a maior parte da memória compartilhada que contém a linha do tempo do vídeo. Essa memória compartilhada é lida por vários threads de trabalho, designados para renderizar filmes em arquivos. Todos eles requerem apenas um identificador (por exemplo, um ponteiro) para uma área de memória para ler os dados dessa área de memória e enviar os quadros renderizados para o disco.
As coisas funcionam perfeitamente, desde que dois ou mais threads sejam lidos no mesmo local de memória. Os problemas surgem quando pelo menos um deles grava na memória compartilhada, enquanto outros lêem a partir dela. Dois problemas podem ocorrer neste momento: Dois ou mais threads lendo do mesmo local de memória podem funcionar sem problemas. O problema surge quando pelo menos uma memória está gravando na memória compartilhada enquanto outras lêem dela. Dois problemas podem surgir neste momento:
-
corrida de dados — enquanto um thread de gravação modifica a memória, um thread de leitura pode estar lendo a partir dela. Se o escritor ainda não tiver terminado o seu trabalho, o leitor obterá dados corrompidos; Corrida de dados - enquanto o thread de escrita está modificando a memória, o thread de leitura pode estar lendo a memória. Se o autor não tiver feito o seu trabalho, o leitor obterá dados corrompidos;
-
condição de corrida — um thread de leitor deve ler somente depois que um escritor tiver escrito. E se acontecer o contrário? Mais sutil do que uma corrida de dados, uma condição de corrida consiste em dois ou mais threads realizando seu trabalho em uma ordem imprevisível, quando na verdade as operações deveriam ser executadas na sequência adequada para serem feitas corretamente. Seu programa pode acionar uma condição de corrida mesmo que tenha sido protegido contra corridas de dados. Condição de corrida - o tópico de leitura só deve ser lido depois que o escritor tiver escrito. E se acontecer o contrário? Mais sutil do que uma corrida de dados, uma condição de corrida ocorre quando dois ou mais threads executam seu trabalho em uma ordem imprevisível, quando na verdade as operações deveriam ser executadas na ordem correta para serem executadas corretamente. Seu programa pode acionar uma condição de corrida mesmo se estiver protegido contra corridas de dados.
O conceito de segurança de thread
O conceito de segurança de thread
Diz-se que um trecho de código é thread-safe se funcionar corretamente, ou seja, sem corridas de dados ou condições de corrida, mesmo que muitos threads o executem simultaneamente. Você deve ter notado que algumas bibliotecas de programação se declaram seguras para threads: se você estiver escrevendo um programa multithread, você deseja ter certeza de que qualquer outra função de terceiros possa ser usada em diferentes threads sem causar problemas de simultaneidade. Se um trecho de código funcionar corretamente, ou seja, sem corridas de dados ou condições de corrida, ele será thread-safe, mesmo que muitos threads o executem simultaneamente. Você deve ter notado que algumas bibliotecas de programação se declaram seguras para threads: se você estiver escrevendo um programa multithread, você deseja ter certeza de que quaisquer outras funções de terceiros possam ser usadas em diferentes threads sem causar problemas de simultaneidade.
A causa raiz da corrida de dados
A causa raiz da competição de dados
Sabemos que um núcleo de CPU pode executar apenas uma instrução de máquina por vez. Diz-se que tal instrução é atômica porque é indivisível: não pode ser dividida em operações menores. A palavra grega “átomo” (ἄτομος; atomos) significa incortável. Sabemos que um núcleo de CPU só pode executar uma instrução de máquina por vez. Tal instrução é chamada de instrução atômica porque é indivisível: não pode ser dividida em operações menores. A palavra grega “átomo” (ἄτομος; atomos) significa tudo.
A propriedade de ser indivisível torna as operações atômicas seguras para threads por natureza. Quando um thread executa uma gravação atômica em dados compartilhados, nenhum outro thread pode ler a modificação pela metade. Por outro lado, quando um thread executa uma leitura atômica em dados compartilhados, ele lê o valor inteiro conforme apareceu em um único momento. Não há como um thread escapar de uma operação atômica, portanto, nenhuma corrida de dados pode acontecer. A natureza indivisível torna as operações atômicas inerentemente seguras para threads. Quando um thread executa uma gravação atômica em dados compartilhados, nenhum outro thread pode ler as modificações parcialmente concluídas. Por outro lado, quando um thread executa uma leitura atômica em dados compartilhados, ele lê todo o valor que ocorre em algum momento. Threads não podem operar atomicamente, portanto corridas de dados não são possíveis.A má notícia é que a grande maioria das operações existentes não são atômicas. Mesmo uma atribuição trivial como x = 1 em algum hardware pode ser composta de múltiplas instruções de máquina atômica, tornando a atribuição em si não atômica como um todo. Portanto, uma corrida de dados é acionada se um thread lê x enquanto outro executa a atribuição. A má notícia é que a grande maioria das operações não é atômica. Mesmo uma atribuição simples como x = 1 em algum hardware pode consistir em múltiplas instruções de máquina atômica, tornando a atribuição em si não atômica como um todo. Portanto, se um thread ler x enquanto outro thread executa a atribuição, uma corrida de dados será acionada.
A causa raiz das condições de corrida
Causa raiz das condições de corrida
A multitarefa preemptiva dá ao sistema operacional controle total sobre o gerenciamento de threads: ela pode iniciar, parar e pausar threads de acordo com algoritmos de agendamento avançados. Você, como programador, não pode controlar o tempo ou a ordem de execução. Na verdade, não há garantia de que um código simples como este: A multitarefa preemptiva dá ao sistema operacional controle total sobre o gerenciamento de threads: ela pode iniciar, parar e pausar threads com base em algoritmos de agendamento avançados. Como programador, você não tem controle sobre o tempo ou a ordem de execução. Na verdade, não há garantia de que um código tão simples:
writer_thread.start()
reader_thread.start ()
iniciaria os dois threads nessa ordem específica. Execute este programa várias vezes e você notará como ele se comporta de maneira diferente em cada execução: às vezes o thread do escritor começa primeiro, às vezes o leitor o faz. Você certamente atingirá uma condição de corrida se o seu programa precisar que o gravador sempre seja executado antes do leitor. Dois threads serão iniciados em uma ordem específica. Execute este programa várias vezes e você notará como ele se comporta de maneira diferente em cada execução: às vezes o thread de escrita começa primeiro, às vezes o thread de leitura começa primeiro. Se o seu programa exige que os escritores sempre executem antes dos leitores, você certamente terá uma condição de corrida.
Esse comportamento é chamado de não determinístico: o resultado muda a cada vez e você não pode prevê-lo. Depurar programas afetados por uma condição de corrida é muito chato porque nem sempre é possível reproduzir o problema de forma controlada. Esse comportamento é chamado de não determinismo: o resultado muda sempre e você não pode prevê-lo. Depurar um programa afetado por condições de corrida é muito chato porque nem sempre é possível reproduzir o problema de forma controlada.
Ensine threads a se darem bem: controle de simultaneidade
Ensinando threads a se dar bem: controle de simultaneidade
Tanto a corrida de dados quanto as condições de corrida são problemas do mundo real: algumas pessoas até morreram por causa deles. A arte de acomodar duas ou mais threads simultâneas é chamada de controle de simultaneidade: sistemas operacionais e linguagens de programação oferecem diversas soluções para cuidar disso. Os mais importantes: Corridas de dados e condições de corrida são problemas do mundo real: algumas pessoas até morreram por causa delas. O ato de acomodar dois ou mais threads simultâneos é chamado de controle de simultaneidade: sistemas operacionais e linguagens de programação fornecem diversas soluções para lidar com isso. O mais importante é:
-
sincronização — uma forma de garantir que os recursos serão usados por apenas um thread por vez. A sincronização consiste em marcar partes específicas do seu código como “protegidas” para que dois ou mais threads simultâneos não o executem simultaneamente, bagunçando seus dados compartilhados; Sincronização – Um método para garantir que apenas um thread use um recurso por vez. A sincronização marca uma parte específica do código como “protegida” para que dois ou mais threads simultâneos não possam executá-la ao mesmo tempo, corrompendo assim os dados compartilhados;
-
operações atômicas — um monte de operações não atômicas (como a tarefa mencionada antes) podem ser transformadas em operações atômicas graças a instruções especiais fornecidas pelo sistema operacional. Desta forma os dados compartilhados são sempre mantidos em estado válido, não importando como outras threads os acessam; Operações atômicas - Graças a instruções especiais fornecidas pelo sistema operacional, muitas operações não atômicas (como a atribuição mencionada anteriormente) podem ser convertidas em operações atômicas. Dessa forma, não importa como outros threads acessam os dados compartilhados, os dados compartilhados sempre permanecem em um estado válido;
-
dados imutáveis — os dados compartilhados são marcados como imutáveis, nada pode alterá-los: os threads só podem lê-los, eliminando a causa raiz. Como sabemos, os threads podem ler com segurança o mesmo local de memória, desde que não o modifiquem. Esta é a principal filosofia por trás da programação funcional. Dados imutáveis - Os dados compartilhados são marcados como imutáveis, nada pode alterá-los: os threads só podem lê-los, eliminando a causa raiz. Sabemos que threads podem ler dados com segurança do mesmo local de memória, desde que não os modifiquem. Este é o princípio principal por trás da programação funcional.
Abordarei todos esses tópicos fascinantes nos próximos episódios desta minissérie sobre concorrência. Fique atento! No próximo episódio desta minissérie sobre concorrência, discutirei todos esses tópicos fascinantes. Por favor, fique atento!
Texto original: https://www.internalpointers.com/post/gentle-introduction-multithreading
What to read next
Want more posts about Translation?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Translation?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home