Por Mariane e Miriam
O que são Threads?
Uma thread é um pequeno programa que trabalha como um subsistema independente de um programa maior e que executa alguma tarefa específica. Um programa dividido em várias threads pode rodar mais rápido que um programa monolítico, pois várias tarefas podem ser executadas ao mesmo tempo. As threads de um programa podem trocar dados entre si e compartilhar o mesmo espaço de memória e os mesmos recursos do sistema.
Uma thread (linha de execução) permite que o usuário do programa, por exemplo, utilize uma funcionalidade do ambiente enquanto outra thread realiza outros cálculos e operações.
Em hardwares equipados com uma única CPU, cada thread é processada de forma aparentemente simultânea, pois a mudança entre uma linha e outra é feita de forma tão rápida que para o usuário isso está acontecendo paralelamente. Em hardwares com múltiplos CPUs ou multi-cores as threads podem ser realizadas realmente de forma simultânea.
Os sistemas que suportam apenas uma thread são chamados de monothread e aqueles sistemas que suportam múltiplas threads são chamados de multithread.
Um benefício do uso de threads (linha de execução) além do fato do processo poder ser dividido em mais de uma linha de tarefas é que 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 linha de execução entra no estado de bloqueio uma outra thread aguarda na fila de “prontos para executar”.
A comunicação entre threads é muito rápida por que elas compartilham tudo: espaço de endereçamento, variáveis globais, memória, etc. Desse jeito elas aumentam o desempenho da aplicação, pois não envolvem mecanismos lentos de intercomunicação.
Deve-se tomar cuidado com o numero de threads criadas. O excesso pode causar overhead no sistema ocasionando uma queda de desempenho.
Estados de uma Thread
Uma thread pode assumir os seguintes estados:
Ø Criação: o processo pai está criando a thread que é levada a fila de “prontos para executar”;
Ø Execução: a thread está usando a CPU;
Ø Pronto: a thread avisa a CPU que pode entrar no estado de execução e entra na fila de “prontos para executar”;
Ø Bloqueado: por algum motivo, a CPU bloqueia a thread, geralmente enquanto aguarda algum dispositivo de I/O;
Ø Término: são desativados os contextos de hardware e a pilha é deslocada.
Tipos Implementação de Threads
As threads podem ser oferecidas por uma biblioteca de rotinas fora do núcleo do sistema operacional (modo usuário), pelo próprio núcleo do sistema (modo Kernel), por uma combinação de ambos (modo híbrido) ou por um modelo conhecido como Scheduler Activations.
Modo Usuário (TMU)
As threads são implementadas pela aplicação e não pelo sistema operacional. Como ele não sabe da existência de múltiplas threads cabe a aplicação gerenciar e sincronizar as diversas threads existentes.
Existem vantagens e desvantagens em todos os casos:
Vantagens
Ø Permitem a implementação de threads mesmo em sistemas operacionais que não suportam threads;
Ø Como dispensam o acesso ao kernel do sistema, são rápidos e eficientes.
Desvantagens
Ø O sistema operacional gerencia o processo como se houvesse uma única thread. Quando uma thread chama uma rotina do sistema que o coloca em estado de espera, todo o processo é colocado em estado de espera, mesmo havendo outras threads prontas para execução;
Ø Tratamento individual de sinais. Como o sistema operacional reconhece apenas processos e não threads, os sinais enviados para um processo devem ser reconhecidos e encaminhados a cada thread para tratamento;
Ø Redução do grau de paralelismo. O sistema seleciona apenas processos para execução e threads de um processo podem ser executadas somente em um processador de cada vez.
Modo Kernel (TMK)
São implementadas diretamente pelo núcleo do sistema operacional.
Vantagens
Ø O sistema operacional sabe de cada thread que existe e assim pode escaloná-las individualmente;
Ø Caso haja múltiplos processadores, threads de um mesmo processo podem ser executadas simultaneamente.
Desvantagens
Ø Baixo desempenho. Há varias mudanças no modo de acesso, pois enquanto no modo do usuário todo o tratamento era realizado sem a ajuda do sistema operacional aqui são utilizadas as rotinas do sistema.
Modo Híbrido
Combina vantagens de threads implementadas em modo usuário e threads implementadas em modo Kernel.
Um processo pode ter vários threads em modo Kernel e por sua vez este pode ter vários threads em modo usuário.
Um thread em modo usuário pode ser executado em um thread de modo Kernel e em segundos, ser executado em outro.
Vantagem
Ø Maior flexibilidade.
Desvantagens
Ø Apresenta problemas herdados de ambas as implementações. Por exemplo, uma chamada de bloqueio de uma thread em modo Kernel bloqueia todas as threads de modo usuário, que são colocados em espera;
Ø Reduz o desempenho, pois threads de modo usuário que precisam utilizar diferentes processadores precisam utilizar diferentes threads de modo Kernel.
Scheduler Activations
Ø Os problemas apresentados nos threads de modo híbrido existem devido à falta de comunicação entre os threads em modo usuário e modo kernel;
Ø Usa o melhor do modo usuário e do modo kernel, através de uma estrutura de dados chamada Scheduler Activations;
Ø Alcança um melhor desempenho evitando mudanças de modos de acesso que não sejam necessárias;
Ø Caso uma thread utilize uma chamada ao sistema que o coloque no estado de espera não é necessário que o kernel seja ativado, basta que a própria biblioteca escalone outra thread;
Ø Bibliotecas em modo usuário e kernel se comunicam, trabalhando de forma cooperativa.
Exemplos de Threads
Um exemplo bem simples de uma thread é um jogo onde o mesmo pode ser modelado com linhas de execução diferentes sendo uma para desenho de imagem e outra para áudio. Neste caso há uma thread para tratar da imagem e outra para tratar do áudio. Para o usuário, a imagem é desenhada ao mesmo tempo em que o áudio é emitido pelos auto-falantes, porém para sistemas com uma única CPU, cada linha de execução é processada uma de cada vez, separando por escalonamento em milessegundos os processos que estão sendo executados.
Exemplos em C#
Exemplo 1:
No C# existe um suporte a execução paralela de código, através de multithreading. Um programa em C# começa com uma thread principal que é criada automaticamente pelo CLR do .NET Framework e pelo sistema operacional é a thread("main"). Então o programa se torna multithreading a partir da criação de outras threads a partir dessa thread principal.
Exemplo de código de uma thread, mostrando o compartilhamento de recursos em comum como no caso o Console:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Thread minhaThread = new
Thread(new ThreadStart(Escreve));
minhaThread.Start();
for (int i = 0; i < 1000; i++)
{
Console.Write("_");
}
Console.ReadLine();
}
public static void Escreve()
{
for (int i = 0; i < 1000; i++)
{
Console.Write("-");
}
}
}
}
A thread principal ("main"), cria uma nova thread que irá executar a função
Escreve(), esta função mostrará na tela o hífen"-". Ao mesmo tempo em que isso
ocorre, a thread principal escreve na tela "_" (underline). Note que
repetindo a execução varias vezes, o comportamento não se repete.
Exemplo 2:
Se em uma aplicação Windows Form há uma tarefa que consome muito tempo, e que faz essa programação normalmente, o Form irá travar enquanto a aplicação processa essa tarefa.
Com os threads isso pode ser evitado, pois a aplicação irá processar a tarefa "em paralelo" numa outra thread e o Form poderá ser utilizado normalmente.
Código de uma aplicação Console utilizando threads:
class Program
{
//Primeiro criamos um método “void” que não recebe parâmetros
//Esse método fará o trabalho das Threads
static void Trabalho ()
{
Console.WriteLine("Esse é o ID da Thread: " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Esse é o nome da Thread: " + Thread.CurrentThread.Name);
//Apenas escrevemos o ID e o nome da Thread em execução
}
static void Main(string[] args)
{
//Aqui utilizamos uma delegate que recebe nosso método como parâmetro
//Note que esta delegate só pode receber como parâmetro
//um método void que não recebe argumentos
ThreadStart operacao = new ThreadStart(Trabalho);
//Agora já estamos prontos para criar a Thread
//A Thread deve receber como parâmetro a delegate que indica
//o que deve ser executado quando a Thread for iniciada
Thread minhaThread1 = new Thread(operacao);
//Nome da Thread
minhaThread1.Name = "Exemplo-1";
//Aqui inicializamos a Thread, então ela executará o que
//foi especificado pela delegate operação
minhaThread1.Start();
//Aqui executamos o mesmo código novamente para
//iniciar uma segunda Thread e compararmos os IDs
Thread minhaThread2 = new Thread(operacao);
minhaThread2.Name = "Exemplo-2";
minhaThread2.Start();
Console.ReadLine();
}
}
Exemplo 3:
Como a thread funciona, mostrando seu período em que está processando várias coisas paralelamente, em milessegundos, dando impressão ao usuário de que está funcionando tudo ao mesmo tempo, e o estado de parada.
using System;
using System.Threading;
public class Worker
{
// Este método será chamado quando a thread é iniciada.
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Voláteis, é usado como dica para o compilador que
//esse membro de dados será acessado por várias threads.
private volatile bool _shouldStop;
}
public class WorkerThreadExample
{
static void Main()
{
// Criar o objeto de discussão. Isso não iniciar a thread.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
// Comece a thread.
workerThread.Start();
Console.WriteLine("main thread: Starting worker thread...");
// Repetir até thread ser ativa.
while (!workerThread.IsAlive);
// Coloque o fio condutor para dormir durante 1 milissegundo
//para permitir que o segmento de trabalho para fazer alguns trabalhos:
Thread.Sleep(1);
// Solicite que a thread pare:
workerObject.RequestStop();
// Use o método de associação para bloquear a thread
//atual até que a thread termine.
workerThread.Join();
Console.WriteLine("main thread: W ted.");
}
}
Bibliografia
Sites:
www.ufba.br
www.joaoseixas.blogspot.com
www.fortium.com.br
http://msdn.microsoft.com/pt-br/library/7a2f3ay4.aspx