Thread (em português: fio de execução ou encadeamento de execução) é uma forma como um processo/tarefa de um programa de computador é divido em duas ou mais tarefas que podem ser executadas concorrentemente ("simultâneo"). O suporte à thread é fornecido pelo sistema operacional no caso da linha de execução ao nível do núcleo (), ou implementada através de uma biblioteca de uma determinada linguagem de programação (User-Level Thread, ULT). Uma thread permite, por exemplo, que o utilizador de um programa utilize uma funcionalidade do ambiente enquanto outras linhas de execução realizam outros cálculos e operações.
Em equipamentos (hardwares) que possuem apenas uma única CPU, cada thread é processada de forma aparentemente simultânea, pois a mudança entre uma thread e outra é feita de forma tão rápida que para o utilizador, isso está acontecendo concorrentemente. Em equipamentos com múltiplos CPUs (também chamados multi-cores), as threads são realizadas realmente de forma simultânea.
Os sistemas que suportam uma única thread (em real execução) são chamados de single-thread enquanto que os sistemas que suportam múltiplas threads são chamados de multithread (multitarefa).
Usos Um exemplo simples seria um jogo, que pode ser modelado com linhas de execução diferentes, sendo uma para desenho de imagem e outra para áudio. Neste caso, há um thread para tratar rotinas de desenho e outro thread para tratar áudio; No ponto de vista do usuário, a imagem é desenhada ao mesmo tempo em que o áudio é emitido pelos alto-falantes; Porém, para sistemas com uma única CPU, cada linha de execução é processada por vez.
Vantagens Simplificação do modelo de programação: Em muitas aplicações ocorrem múltiplas atividades simultaneamente, e algumas delas podem ser bloqueadas de tempos em tempos. Ao decompor uma aplicação em múltiplos threads sequenciais que executam quase em paralelo, há uma simplificação do modelo de programação. Threads são mais rápidos de criar e destruir se comparado aos processos, uma vez que não possuem quaisquer recursos associados a eles. Desempenho: quando há grande quantidade de computação e de E/S, os threads permitem que essas atividades se sobreponham, acelerando a aplicação.
Particularidades Cada thread tem o mesmo contexto de software e compartilha o mesmo espaço de memória (endereçado a um mesmo processo-pai), porém o contexto de hardware é diferente. Sendo assim o overhead causado pelo escalonamento de uma thread é muito menor do que o escalonamento de processos. Entretanto, algumas linguagens (C, por exemplo) não fornecem acesso protegido à memória nativa (sua implementação fica a cargo do programador ou de uma biblioteca externa) devido ao compartilhamento do espaço de memória.
Um dos benefícios do uso das threads advém do fato do processo poder ser dividido em várias threads; quando uma thread está à espera de determinado dispositivo de entrada/saída ou qualquer outro recurso do sistema, o processo como um todo não fica parado, pois quando uma thread entra no estado de 'bloqueio', uma outra thread aguarda na fila de prontos para executar.
Uma thread possui um conjunto de comportamentos padrão, normalmente encontrados em qualquer implementação ou sistema operativo. Uma thread pode: criar outra da mesma forma que um processo, através do método thread-create, onde a thread retorna um ID como primeiro argumento (resultado da função de criação); esperar outra thread se sincronizar, através do método join; voluntariamente "desistir" da CPU por não precisar mais do processamento proposto pela própria ou por vontade do utilizador. Feito através do método thread-yield; replicar-se sem a necessidade de duplicar todo o processo, economizando assim memória, processamento da CPU e aproveitando o contexto (variáveis, descritores, dispositivos de I/O).
Itens compartilhados e itens privados Há diferenças e semelhanças nos itens presentes nas threads. Dentro de um processo, todas as threads possuem informações em comum, mas cada thread tem informações pertencentes somente a ela. Exemplos:
Itens compartilhados entre as threads: Espaço de endereçamento; Variáveis globais; Arquivos abertos; Processos filhos; Alarmes pendentes; Sinais e tratadores de sinais; Informação de contabilidade.
Itens privados por thread: Contador de programa; Registradores; Pilha; Estado.
Estados de uma linha de execução Uma thread pode assumir os seguintes estados: Unstarted: logo após ser criada (antes do Start()); Running: após ser ativada (Start()) ou após método Resume(); Suspended: após método Suspended(); Stopped: após método Abort().
ULT e KLT Usualmente as threads são divididas em duas categorias: thread ao nível do utilizador (), e thread ao nível do núcleo ().
Thread em modo usuário Imagem:Ult.jpg
Thread em modo kernel Imagem:Klt.jpg
As threads da primeira categoria (ULT) são suportadas pela aplicação, sem conhecimento do núcleo e geralmente são implementadas por pacotes de rotinas (códigos para criar, terminar, escalonamento e armazenar contexto) fornecidas por uma determinada biblioteca de uma linguagem, como é o caso da thread.h (biblioteca padrão da linguagem C). Estas threads suportam as mesmas operações que as threads KLT (criar, sincronizar, duplicar e abortar). Possuem como vantagens a possibilidade de implementação em sistemas operativos que não suportam nativamente este recurso, sendo geralmente mais rápidas e eficientes pois dispensam o acesso ao núcleo. Evita assim mudança no modo de acesso, e a estrutura de dados fica no espaço do utilizador, levando a uma significativa queda de overhead, além de poder escolher entre as diversas formas de escalonamento em que melhor se adequa.
A gestão da thread (KLT) não é realizada através do código do próprio programa; todo o processo é subsidiado pelo SO. Esse modelo tem a vantagem de permitir o suporte a multiprocessamento e o facto do bloqueio de uma linha de execução não acarretar bloqueio de todo processo, não obstante, temos a desvantagem de ter que mudar o tipo de acesso sempre que o escalonamento for necessário, aumentando assim o tão temido overhead.
Há quatro operações básicas na gestão de threads: criar, terminar, thread join e thread yield.
Criar (thread creation) Basicamente uma thread pode criar outra(s), sendo que depois essas mesmas threads são executas 'simultaneamente'. A thread criadora é a thread-mãe e a thread criada é a thread-filho. Threads incluídas na função main quando executadas podem criar threads-filho. No diagrama a seguir há a thread A que executa inicialmente. Mais tarde é criada a thread B indicada no ponto amarelo. Depois de criadas, a thread A e thread B executam simultaneamente. Em seguida a thread A pode criar uma ou mais threads (por exemplo uma thread C). Depois de criada a thread C, há três threads executando simultaneamente e todas disputam o uso da CPU. Entretanto, a thread que pode ser executada a qualquer momento não é de conhecimento da CPU.
Terminar (thread termination) Para maioria dos casos, as threads não são criadas e executadas eternamente. Depois de terminado o seu objectivo, a thread termina. No facto, a thread que criou estas duas threads-filho termina também, porque sua tarefa atribuída se completa. Na matrix de multiplicação (matrix multiplication), uma vez que o valor de C[i,j] é computado, a thread correspondente termina. Em geral quando a tarefa atribuída a thread completa, a thread pode ser terminada. Além disso, se a thread-mãe terminar, todas as threads filho terminam também. Porque isso é importante? Isso é importante porque as threads-filho compartilham recursos com a thread-mãe, incluindo variáveis. Quando a thread-mãe termina, todas as variáveis são perdidas e a thread-filho não poderá aceder aos recursos que a thread-mãe possuía. Assim, se a thread-mãe terminar mais cedo que a thread-filho haverá um problema! Uma thread pode terminar das seguintes maneiras:
Retornando da sua rotina mais externa, a thread criadora. Quando termina a rotina em que foi começada. Chamando pthread_exit, fornecendo um estado de saída. Terminando através da função pthread_cancel
Sincronizar(Thread Join) Imagine a seguinte situação: Você está estudando para uma prova. Então você pede o seu irmão mais novo para comprar uma pizza. Neste caso você é a thread principal e seu irmão a thread-filho. Uma vez que você deu a ordem, você e seu irmão começam a “executar uma tarefa” simultaneamente. Agora há dois casos a se considerar: Primeiro: Seu irmão traz a pizza e termina enquanto você estuda. Nesse caso você pode parar de estudar e comer a pizza. Segundo: Você acaba de estudar mais cedo e dorme e depois a pizza chegará.
A junção de threads (thread join) é destinada para resolver este problema. A thread pode executar o thread join e aguardar até a outra thread terminar. No caso acima, você é a thread principal (thread main) e deve executar o thread joinaguardando o seu irmão (thread-filho) terminar. Em geral o thread join é utilizado para a thread-mãe se sincronizar com uma das threads-filho.
Thread Yield (Rendimento da thread) Suponha que você executa um certo número de programas o tempo todo no computador. Isso é possível devido a CPU escalonar pouco a pouco outros ciclos da CPU, assim outros programas podem ser executados. Isso pode ser um problema de política de planeamento do Sistema Operativo. Entretanto, quando escrevemos programas com múltiplas threads, temos que fazer correctamente para que algumas threads não ocupem a CPU eternamente, ou por um tempo muito longo sem abandoná-lo. Senão terminar na situação acima quando uma ou duas threads executam enquanto outras simplesmente esperam para retornar. Liberamos espaço na memória graças a thread yield. Quando a thread executa o thread yield, a execução da thread é suspensa e a CPU passa para uma outra thread em execução. Essa thread aguardará até a CPU tornar-se disponível novamente.
Escalonamento Da mesma forma que os processos sofrem escalonamento, com as threads ocorre o mesmo. Quando vários processos e threads são executados em uma CPU, eles dão a impressão que estão sendo executados simultaneamente/concorrentemente, devido esta alternância ser muito rápida.
Linha de execução ao nível do usuário
As ULT são escalonadas pelo programador, tendo a grande vantagem de cada processo usar um algoritmo de escalonamento que melhor se adapte a situação, o sistema operacional neste tipo de thread não faz o escalonamento, em geral ele não sabe que elas existem. Neste modo o programador é responsável por criar, executar, escalonar e destruir a thread. Um exemplo prático de processo chamado P1 que contém tais threads: P1T1, P1T2 e P1T3, quando o sistema operacional da a CPU para o processo P1 cabe a ele destinar qual thread será executada, caso esta thread use todo processo do quantum, o sistema operacional chamará outro processo, e quando o processo P1 voltar a executar, P1T1 voltará a ser executada e continuará executando até seu término ou intervenção de P1, este comportamento não afetará outros processos pois o sistema continua escalonando os processos normalmente.
Linha de execução ao nível do núcleo