Thread
Origem: Wikipédia, a enciclopédia livre.
- Nota: Se procura thread, um termo utilizado na internet, consulte thread (discussão).
Thread, ou linha de execução em português, é uma forma de um processo dividir a si mesmo em duas ou mais tarefas que podem ser executadas simultaneamente. O suporte à thread é fornecido pelo próprio sistema operacional (SO), no caso da Kernel-Level Thread (KLT), ou implementada através de uma biblioteca de uma determinada linguagem, no caso de uma User-Level Thread (ULT).
Uma thread permite que o usuário de programa, por exemplo, utilize uma funcionalidade do ambiente enquanto outras threads realizam outros cálculos e operações.
Os sistemas que suportam apenas uma única thread de execução são chamados de monothread e aqueles sistemas que suportam múltiplas threads são chamados de multithread.
Índice |
[editar] Peculiaridades da Thread
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 thread é muito menor do que o escalonamento de processos, entretanto, não há acesso protegido a memória nativamente (sua implementação fica a cargo do programador) devido ao compartilhamento do espaço de memória.
Um benefício do uso das threads advém do fato do processo poder ser dividido em mais de uma linha de tarefas; quando uma linha está esperando determinado dispositivo de I/O 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 operacional.
- uma thread pode criar outra da mesma forma que um processo, tal advento é conhecido como thread-create, onde a thread retorna um ID ao primeiro como primeiro argumento, como resultado da função de criação.
- uma thread pode esperar outra para se "juntar" (sincronizar), tal advento é conhecido como join.
- uma thread pode voluntariamente "desistir" da CPU, por não ser preciso mais o processamento proposto por ela ou pela vontade do usuário, tal advento é conhecido como thread-yield.
- uma thread pode se "duplicar" 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).
[editar] Estados de uma thread
Basicamente uma thread pode assumir os seguintes estados:
- criação. Neste estado, o processo pai está criando a thread que é levada a fila de prontos;
- execução. Neste estado a thread está usando a CPU;
- pronto. Neste estado a thread avisa a CPU que pode entrar no estado de execução e entra na fila de prontos;
- bloqueado. Neste estado, por algum motivo, a CPU bloqueia a thread, geralmente enquanto aguarda algum dispositivo de I/O;
- término. Neste estado são desativados o contexto de hardware e a pilha é desalocada.
[editar] ULT e KLT
Usualmente as threads são divididas em duas categorias: User-Level Thread (ULT), ou thread ao nível do usuário, e Kernel-Level Thread (KLT), thread ao nível do Kernel.
As threads da primeira categoria são suportadas pela aplicação, sem conhecimento do Kernel e geralmente são implementadas por pacotes de rotinas (códigos para criação, término, escalonamento e armazenagem de 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 (criação, juntar, duplicar e desistir). Possuem como vantagens a possibilidade de implementação em sistemas operacionais que não suportam nativamente este recurso, geralmente são mais rápidas e eficientes pois dispensam o acesso ao kernel, evitando assim mudança no modo de acesso e sua estrutura de dados fica no espaço do usuário, levando a uma significa queda de overhead, além de poder escolher entre as diversas formas de escalonamento em que melhor se adequa.
O gerenciamento de threads (KLT) não é realizado através do código do próprio programa; todo o processo é subsidiado pelo SO. Esse modelo tem a a vantagem de permitir o suporte a multiprocessamento e o fato do bloqueio de uma thread 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.
São cinco operações básicas do gerenciamento de threads: criação da thread, término da thread, thread join e thread yield.
Criação da thread (thread creation)
Basicamente uma thread pode dividir uma linha de execução em duas, depois estas linhas(threads) executam simultaneamente a thread criadora é a thread pai e a thread criada é a thread filho. Threads incluidas na função main quando executadas pode criar threads filho no diagrama a seguir a thread A 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 (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.
Término da thread (thread termination)
Para maioria dos casos as threads não são criadas e executadas eternamente. Depois de terminado seu trabalho, a thread termina. No fato, a thread que criou estas duas threads filho terminam 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 pai terminar, todas as threads filho terminam também. Porque isso é importante? Isso é importante porque as threads filho compartilham recursos com a thread pai, incluindo variáveis. Quando a thread pai termina, todas as variáveis são perdidas e a thread filho não poderá acessar os recursos que a thread pai possui. Assim, se a thread pai termina 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
Junção de threads (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 filha. Uma vez que você deu a ordem você e seu irmão começam a “executar uma tarefa” sumultaneamente. 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 join aguardando o seu irmão (thread filho) terminar. Em geral o thread join é utilizado para a thread pai juntar com uma das threads filhas.
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 destruir pouco a pouco os ciclos de CPU assim outros programas podem ser mal executados. Isso pode ser um problema de política de planejamento do sistema operacional. Entretanto, quando nós escrevemos nossos programas com múltiplas threads, nós temos que fazer certo para que algumas threads não ocupem a CPU eternamente, ou por um tempo muito longo sem abandona-lo. Se não terminará na situação acima quando uma ou duas threads executam enquanto outras simplesmente esperam para retornar. Isto é, nós executamos nossas threads com muita “cortesia” caminho uma vez que enquanto a thread pega o restante da CPU para ser usado por outras threads. Isso acontece 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.
[editar] Comparação entre Thread e Processo
Um sistema baseado em threads é diferente de um sistema operacional multi-tarefa tradicional, em que processos são tipicamente independentes, carregam considerável estado da informação, tem endereço de memória separado e interagem somente através de mecanismos de inter-processos de comunicação. As threads, por outro lado, compartilham o estado da informação de processos únicos, e compartilham memória e outros recursos diretamente.
A troca de contexto através de threads num mesmo processo é tipicamente mais rápida que a troca de contexto entre processos diferentes. Sistemas como o Windows NT e o OS/2 são feitos para ter threads "baratas" e processos "caros", enquanto em outros sistemas operacionais não há grandes diferenças.
O multithreading é um modelo de programação popular que permite a execução de múltiplas threads dentro de um contexto simples, compartilhando recursos do processo, e capazes de executar de forma independente. O modelo de programação em thread fornece ao desenvolvedor uma execução simultânea. Entretanto, a aplicação mais interessante da tecnologia ocorre quando ela é utilizada em um processo simples permitindo uma execução paralela em sistemas multi-processados.
Um sistema multi-threaded possui um melhor desempenho que um sistema de computadores com múltiplas CPUs e com múltiplos núcleos, ou que um cluster de máquinas. Isto acontece porque a thread empresta a ela mesmo uma execução simultânea. Em alguns casos, o programador precisa ter cuidado em evitar condições de concorrência e outros comportamentos inesperados.
Para um dado ser manipulado corretamente, as threads freqüentemente precisarão ser sincronizadas, para que os dados sejam processados na ordem correta. As threads podem também executar operações atômicas (freqüentemente implementadas usando semáforos) com intuito de prevenir que dados comuns sejam simultaneamente modificados ou lidos enquanto o processo esta sendo modificado.
Os sistemas operacionais implemetam as threads de duas formas: preempção multithreading ou multithreading cooperativa. A preempção multithreading é geralmente considerada uma implementação superior, porque permite ao sistema determinar quando uma troca de contexto pode acontecer. A multithreading cooperativa, por outro lado, confia nas threads para ceder o controle, uma vez que elas estão paradas em um ponto. Isto pode criar um problema se a thread estiver esperando um recurso tornar-se disponível. A desvantagem da preempção multithread é que o sistema pode fazer uma troca em um tempo inapropriado, causando uma inversão de prioridade ou outros efeitos ruins que podem ser evitados por uma multithreading cooperativa.
Em geral:
• Criar um processo pode ser caro em termos de tempo, memória, e sincronização entre processos.
• Threads podem ser criadas sem a replicação do processo inteiro.
• O trabalho de criar uma thread pode ser feito no espaço do usuário.
• Como as threads partilham o espaço de endereçamento a comunicação entre elas é mais rápida.
• O tempo gasto para troca de threads é menor, em parte por que não há necessidade de troca de espaço de endereçamento.
[editar] Exemplo (em Java)
import java.io.*; public class Example implements Runnable { static Thread threadCalculate; static Thread threadListen; long totalPrimesFound = 0; public static void main (String[] args) { Example e = new Example(); threadCalculate = new Thread(e); threadListen = new Thread(e); threadCalculate.start(); threadListen.start(); } public void run() { Thread currentThread = Thread.currentThread(); if (currentThread == threadCalculate) calculatePrimes(); else if (currentThread == threadListen) listenForStop(); } public void calculatePrimes() { int n = 1; while (true) { n++; boolean isPrime = true; for (int i = 2; i < n; i++) if ((n / i) * i == n) { isPrime = false; break; } if (isPrime) { totalPrimesFound++; System.out.println(n); } } } private void listenForStop() { BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String line = ""; while (!line.equals("stop")) { try { line = input.readLine(); } catch (IOException exception) {} } System.out.println("Found " + totalPrimesFound + " prime numbers before you said stop"); System.exit(0); } }