Um thread ou fio (em execução) ou tarefa (termo e definição padronizada pela ISO / IEC 2382-7: 2000 Outros nomes conhecidos: processo leve , thread de instrução , processo simplificado , exétron ou unidade de execução ou tratamento de unidade ) é semelhante a um processo porque ambos estão executando um conjunto de instruções na linguagem de máquina de um processador . Do ponto de vista do usuário, essas execuções parecem ocorrer em paralelo . No entanto, onde cada processo possui sua própria memória virtual , os threads do mesmo processo compartilham sua memória virtual. No entanto, todos os threads têm sua própria pilha de execução .
Os threads são normalmente usados com a GUI ( interface gráfica do usuário ) de um programa para expectativas assíncronas em telecomunicações ou para programas HPC (como codificação de vídeo, simulações matemáticas, etc.).
De fato, no caso de uma interface gráfica, as interações do usuário com o processo, por intermédio dos dispositivos de entrada, são gerenciadas por um thread , técnica semelhante à utilizada para esperas assíncronas, enquanto cálculos pesados (em termos de tempo de computação) são controlados por um ou mais outros threads . Esta técnica de design de software é vantajosa neste caso porque o usuário pode continuar a interagir com o programa mesmo quando ele está executando uma tarefa . Uma aplicação prática é encontrada em processadores de texto onde a verificação ortográfica é realizada enquanto permite que o usuário continue inserindo seu texto. O uso de roscas possibilita, portanto, tornar o uso de uma aplicação mais fluido, pois não há mais bloqueios durante as fases intensas de processamento.
No caso de um programa de computação intensivo, a utilização de vários threads permite paralelizar o processamento, o que, em máquinas multiprocessadas , permite que seja realizado com muito mais rapidez.
Dois processos são completamente independentes e isolados um do outro. Eles só podem interagir por meio de uma API fornecida pelo sistema, como o IPC , enquanto os threads compartilham informações sobre o estado do processo, áreas de memória e outros recursos. Na verdade, além de sua pilha de chamadas , mecanismos como Thread Local Storage e algumas raras exceções específicas para cada implementação, os threads compartilham tudo. Como não há mudança na memória virtual , a troca de contexto ( troca de contexto ) entre dois threads consome menos tempo do que a troca de contexto entre dois processos. Isso pode ser visto como uma vantagem de programar usando vários threads .
Em certos casos, os programas que usam threads são mais rápidos do que os programas de arquitetura convencional, em particular nas máquinas com vários processadores . Além da questão do custo da troca de contexto , a maior sobrecarga devido ao uso de vários processos vem da comunicação entre processos separados. Na verdade, o compartilhamento de recursos entre threads permite uma comunicação mais eficiente entre as diferentes threads de um processo do que entre dois processos separados. Onde dois processos separados devem usar um mecanismo fornecido pelo sistema para se comunicar, os threads compartilham parte do estado do processo, incluindo sua memória. No caso de dados somente leitura, não há necessidade de nenhum mecanismo de sincronização para que os threads usem os mesmos dados.
A programação usando threads , no entanto, é mais rigorosa do que a programação sequencial , e o acesso a alguns recursos compartilhados deve ser restrito pelo próprio programa, para evitar que o estado de um processo se torne temporariamente inconsistente, enquanto que outra thread vai precisar ver isso parte do estado do processo. Portanto, é obrigatório configurar mecanismos de sincronização (usando semáforos , por exemplo), tendo em mente que o uso de sincronização pode levar a situações de deadlock se for incorreto.
A complexidade dos programas que usam threads também é significativamente maior do que a dos programas que adiam sequencialmente o trabalho a ser feito para vários processos mais simples (a complexidade é semelhante no caso de vários processos trabalhando em paralelo). Esse aumento de complexidade, quando mal gerenciado durante a fase de design ou implementação de um programa, pode levar a vários problemas, como:
Sistemas operacionais geralmente implementar os tópicos , muitas vezes chamado de fio sistema ou threads nativas (em oposição a tópicos relacionados com a linguagem de programação dado). Eles são usados por meio de uma API específica para o sistema operacional, por exemplo, Windows API ou POSIX Threads . Em geral, essa API não é orientada a objetos e pode ser relativamente complexa de implementar adequadamente porque é composta apenas de funções primitivas , o que geralmente requer algum conhecimento da operação do escalonador .
A vantagem dos threads nativos é que eles permitem o desempenho máximo, pois sua API ajuda a minimizar o tempo gasto no kernel e eliminar camadas de software desnecessárias. Sua principal desvantagem é, pela natureza primitiva da API, uma maior complexidade de implementação.
Algumas linguagens de programação, como Smalltalk e algumas implementações de Java , integram suporte para threads implementados no espaço do usuário ( threads verdes (in) ), independentemente dos recursos do sistema operacional host.
A maioria das linguagens ( Java na maioria dos sistemas operacionais, C # .NET , C ++ , Ruby ...) usa extensões de linguagem ou bibliotecas para usar diretamente os serviços multithreading do sistema operacional, mas de forma portátil. Por fim, linguagens como Haskell usam um sistema híbrido no meio do caminho entre as duas abordagens. Observe que, por motivos de desempenho dependendo das necessidades, a maioria dos idiomas permite a escolha de fios nativos ou fios verdes (em particular por meio do uso de fibras ). Outras linguagens, como Ada (linguagem) , também implementam multitarefa independente do sistema operacional sem realmente usar o conceito de threading .
C ++, desde o novo padrão C ++ denominado C ++ 11 , também possui uma biblioteca de gerenciamento de threads (da Boost ): o modelo de classe é std::thread. Isso é fácil de usar e permite que você crie e execute seus threads . Anteriormente, cada estrutura precisava implementar sua própria sobreposição de gerenciamento de encadeamento , geralmente conectada diretamente aos encadeamentos nativos do sistema.
Na programação paralela, o princípio básico é garantir a reentrada das entidades de software usadas pelos threads , seja por design ( funções puras ) ou por sincronização (em particular pelo uso de um mutex em torno da chamada para a função.).
Programação processualNa programação procedural, os threads nativos do sistema operacional são usados diretamente na maioria das vezes . Seu uso é então diretamente dependente da API do sistema, com suas vantagens e desvantagens. Em particular, as funções usadas pelos threads devem ser reentrantes ou protegidas por mutex.
Programação orientada a objetosNa programação orientada a objetos, o uso de threads geralmente é feito por herança de uma classe pai genérica (ou modelo de classe), tendo um método virtual puro que conterá o código a ser executado em paralelo. Você tem que escrever e então apenas escrever a classe derivada implementando o que você deseja paralelizar, instanciá-la e chamar um método particular (freqüentemente chamado de Run ou equivalente) para iniciar o thread . Também existem métodos de parar ou aguardar o fim da tarefa, simplificando muito a criação de threads simples. No entanto, operações mais complexas ( barreira em um grande número de threads , configurações de prioridade precisas , etc.) podem ser menos fáceis do que usar threads nativas.
Falamos de uma classe reentrante quando instâncias separadas dessa classe podem ser usadas por threads sem efeitos colaterais , o que geralmente significa a ausência de um elemento global ou estático na implementação da classe. Também falamos de uma classe thread-safe quando vários threads podem usar uma única instância dessa classe sem causar problemas de simultaneidade. Uma classe thread-safe é necessariamente reentrante, mas o inverso é falso.
Programação funcionalPor natureza, uma vez que tudo na programação funcional é reentrante - e freqüentemente thread-safe - com algumas raras exceções, a implementação de threads é bastante simplificada por este paradigma. Em geral, a paralelização do código depende apenas dos contratos e das sequências de chamadas: é claro que exige calcular antes , o que impede paralelizar as duas funções. Mas essas restrições não são específicas da programação funcional, elas são inerentes ao algoritmo implementado.
Muitos padrões podem se beneficiar de threads , como Decorator , Factory Method , Command ou Proxy . Em geral, qualquer passagem de informação para uma entidade de software de um padrão pode ser objeto do uso de threads .
Outros exemplos clássicos incluem:
A tecnologia Hyper-Threading de alguns processadores Intel não deve ser confundida com threads . Essa tecnologia permite a execução simultânea de processos separados e threads . Qualquer máquina com vários processadores ( SMP ) ou processadores que integrem Hyper-Threading permite a execução mais rápida de programas usando threads , bem como vários processos.