Este artigo engloba todas as funcionalidades das threads dentro do sistema operacional e dentro de futuros softwares que vocês desejarem construir. Aqui usarei exemplos em Python para poder demonstrar a funcionalidade das threads dentro do nosso dia a dia do desenvolvimento.
Agora vamos ver como funciona a criação de threads dentro do Python 3.1, logo após entendermos o que é uma thread vamos entender como as threads dentro do Python funcionam.
Dentro do Python as funções de threads estão dentro de uma classe chamada threading.Threads, esta classe tem todas as funções necessárias para manutenção de threads dentro do Python, antes de utilizá-la você deve importar o modulo threading usando "import. threading", mas para podermos utilizá-la precisamos criar uma nova classe que irá herdar da classe threading.Threads da seguinte forma:
Nesta linha de código você acabou de herdar todos os métodos da classe threading.Thread. Após você ter feito a herança você deve adicionar uma linha no seu construtor __init__, a linha a ser adicionada deve ser:
Esta linha será a responsável por inicializar a thread dentro do SO. Após isso, e após a criação de todos os métodos você deve criar mais um método chamado run(self): este método é o que vai executar sua thread, este método é sobrecarregado da classe pai, onde dentro dele você deve fazer as chamadas de funções ou de outras classes, pois devemos lembrar que esta classe é apenas uma classe para threads, dentro dela que você pode inserir seus cálculos etc.
O exemplo que eu vou apresentar agora é uma forma de calcular fatoriais usando threads, como Python é uma linguagem filha do Haskell (linguagem funcional) o cálculo do fatorial mesmo usando um for é extremamente rápido. Por isso usamos a classe threading.Timer para ter uma demora maior como se estivesse calculando por mais tempo apenas para perceber o resultado final.
Código do programa
Neste código podemos ver que o construtor (__init__) recebe dois parâmetros, o parâmetro number é o valor que será calculado. O parâmetro time, que define quanto tempo essa thread vai demorar a retornar o valor. Após isso temos o método run(self), nele executamos o fatorial.
Após o for do fatorial temos a declaração da classe threading.Timer(), esta classe recebe dois parâmetros de entrada. O primeiro é o tempo que vai ser executada o segundo parâmetro é a função que deve ser executada depois desse tempo determinado. Como o nosso cálculo é feito dentro da run() foi criada uma função timef(self) sem execução para poder criar o objeto. Após a criação devemos chamar o método t.start() para iniciar a Thread de tempo. Mas, se você apenas mandar iniciar essa thread, sua classe irá finalizar e irá imprimir na tela o resultado e o tempo irá correr em paralelo. Para apresentar o resultado só quando o objeto t terminar o tempo devemos chamar o método t.join() onde irá continuar o código só quando essa Thread terminar. Após isso nosso método é finalizado.
Agora é só fazer uma chamada para essa classe e ver o resultado, segue um exemplo que eu fiz:
Executem este exemplo e boa sorte com as threads. Espero que tenham gostado!
E também, em C++, quando sair a nova versão do padrão, threads vai ser parte da linguagem, algo padronizado que deverá funcionar em todos os compiladores, e o método de se trabalhar com elas não é nem orientado a classes e parecido com o POSIX threasd, mas é bem interessante e usa templates: http://en.wikipedia.org/wiki/C%2B%2B0x#Threading_facilities
[3] Comentário enviado por salviof em 26/08/2010 - 08:48h
Não adianta nada o sistema ser multprocessado no caso do windows, o windows separa a execução do executavel por nucleo, e você pode ter mil Threads rodando ao mesmo tempo que elas vão ser executadas em um único processador, e não dividida entre os outros processadores...
Me corrija se eu estiver errado, mas em testes verifiquei que mesmo com muitas threads o maximo que consegui ocupar foi 1 núcleo....
Mas a utilizaçõa de threads é essencial para não travar a tela do sistema enquanto estiver executando uma tarefa pesada (com o aviso: este programa não está respondendo), ou se precisar executar várias tarefas ao mesmo tempo....
[4] Comentário enviado por TRBaldim em 26/08/2010 - 09:10h
Salviof,
Sim, esta forma que apresentei no artigo ela usa o mesmo núcleo... Há várias formas de aplicar threads dentro de vários núcleos. No caso de Python ainda não sei como fazer. Mas temos uma liguagem chamada ADA que ela pode ser aplicada aos vários núcleos. C também pode ser aplicado a vários núcleos. Acho que Java também dá para fazer. Irei pesquisar mais sobre Python mas ótima observação.
[5] Comentário enviado por stremer em 26/08/2010 - 14:29h
amigo, desculpa mas acho que esta meio confuso...
uma coisa é thread outra é multi-task...
pelo que eu saiba thread existe a muito tempo
e no ms-dos já tinhamos programas multi-thread. (não threads posix como falado abaixo, mas programas capazes de realizar mais de uma tarefa ao mesmo tempo, um tipo de thread simulada, mas não como processo pois não havia isolamento também como falado)
Agora multi-task é outra coisa, embora possivel também no dos com softwares de terceiros (que faziam uma série de gambis internas).
Multi-task é coisa do sistema operacional, rodar varios processos em paralelo.
Cada processo pode possuir uma ou muitas tarefas (chamadas também de threads mas não é só com elas que conseguimos paralelismo).
O sistema operacional que também deve ser compativel com multi-processamento, e mesmo que o programa seja o mais burro para rodar em apenas um nucleo, o sistema operacional tem que fazer muito mais coisa para justificar varios nucleos...
Agora o programa multi-thread rodar em varios nucleos é um pouco mais complicado (outra coisa estranha na sua explicação que cita que se o programador não fizer multi-thread de nada adianta ter 2 ou mais nucleos), neste caso, sei que o java consegue fazer isso com o parâmetro -XX:+UseParallelGC neste caso o garbage collector esta implementado para rodar em mais nucleos tornando o desempenho do programa melhor como um todo já que a VM é responsavel por grande parte da execução do programa.
Ou seja, isso também NÃO É LIMITAÇÃO DO WINDOWS e SIM DO PROGRAMA!
[6] Comentário enviado por stremer em 26/08/2010 - 14:47h
resumindo: lendo agora parece que ficou meio confuso: De nada adianta o programa ser multi-thread se dentro dele as partes pesadas forem sequenciais...
por isso um processo pode ter 20 threads e estar utilizando apenas 50% do cpu... pois esta preso em um processo sequencial...
com o java, usando o parallelGC, como a VM é responsavel por alocação/desalocação de memória, toda esta parte multi-thread acaba utilizando mais nucleos, pois senão acaba dando gargalo na VM...
[7] Comentário enviado por stremer em 26/08/2010 - 15:08h
PS: No MS-DOS... as threads eram meio que "simuladas" no programa... pois o sistema nativamente suportava apenas um processo (não era multi-task) e não suportava threads nativas....
haviam bibliotecas que faziam isso.
A mesma coisa paara multi-task existia o desqview...
[8] Comentário enviado por TRBaldim em 26/08/2010 - 15:21h
Vamos lá, quando eu falo em usar um programa em threads estamos falando em executar várias tarefas ao mesmo tempo. Crie um programa que compacta 10 arquivos ao mesmo tempo. Você vai se você não usa threads você terá que fazer uma a um dentro. Agora você fazendo threads você pode aplicar em várias tarefas simultâneas. Agora se o WINDOW$ não consegue fazer isso... Eu já não sabia... Não entendo muito da funcionalidade dele... Mas vamos falar de um SO que trabalha MUITO bem com vários processadores e vários núcleos. Como eu conheço bem Linux e Unix digo que eles podem redistribuir as aplicações dentro dos nucleos ou processadores. Não sabia que Windows era tão burro a ponto de não saber fazer nada disso... Mas que seja e o seu parâmetro -XX:+UseParallelGC só faz o garbage collector rodar em uma thread paralela ao seu programa para limpar a memória mais eficientemente... Mas a distribuição no processador não é responsabilidade da JVM mas sim do SO... A JVM trabalha diretamente com a memória... Ela não tem uma linguagem de máquina tão baixa assim... Em geral a forma de se programar em threads tem muitas e muitas e muitas... O que eu demonstrei foi como criar uma thread... Mas dentro as minhas pesquisas para eu poder ter mais de uma thread separadamente por processo tem que ser feito separadamente e com outras formas de se chamar o programa...
[9] Comentário enviado por stremer em 26/08/2010 - 16:24h
o problema que as vezes mesmo programando em paralelo, há outros gargalos que impedem que o SO redistribua.... por isso falei que não é tão simples assim....
por isso falei que a culpa não é do windows....
No caso do useparallelgc é uma forma de implementação do garbage que é própria para rodar em paralelo e como no java toda a alocação/desalocação é feita pela JVM, o funcionamento do garbage influencia diretamente na performance do programa...
[10] Comentário enviado por psfdeveloper em 26/08/2010 - 20:40h
Parece que o pessoal está confundindo um pouco multi-tasking e multi-threading. Vou tentar explicar o que eu aprendi a esse respeito...
Multi-tasking (multi-tarefa) é a capacidade de um sistema operacional executar mais de um programa simultaneamente. O multi-tasking pode ser cooperativo (o que quase não existe mais porque é obsoleto), quando é responsabilidade do próprio programa passar a execução para os outros ou preemptivo, quando o sistema operacional controla quanto de processamento cada programa vai utilizar dependendo de sua prioridade dentro de uma estrutura pertencente ao sistema operacional chamada de "scheduler".
Multi-threading é simplesmente uma forma de multi-tasking. Existem dois tipos de processos, os heavyweight processes, que são os nossos velhos conhecidos programas, que consistem de uma linha de execução, a thread, em um espaço de memória reservado apenas a ela. Isso é chamado de Process Isolation. Um programa POSIX pode criar novos heavyweight processes usando a syscall fork.
E existem os lightweight processes, que são várias linhas de execução, ou várias threads, compartilhando o mesmo espaço de memória, ou seja, dentro do mesmo programa. Esse último caso (lightweight process) é o que ficou convencionalmente sendo chamado de programas com multi-threading, o que não é uma convenção enganosa. Para se criar threads, pode-se usar a rotina pthread_create do da biblioteca POSIX Threads. pthread_create é uma função da biblioteca C do Linux (e de vários outros Sistemas Operacionais Unix Like - incluindo o Mac OS X!). A criação de Threads a partir de Syscalls do Linux não é trivial e a forma padrão de se criar programas multi-thread no linux é via POSIX Threads.
Geralmente o sistema operacional distribui as threads entre os processadores, mas duas coisas podem impedir tal distribuição. A primeira é o uso de recursos de sistema compartilhados como Locks e outras ferramentas de sincronização. Outra possibilidade é a existência de algum programa externo rodando paralelamente e executando em alta prioridade, o que leva o sistema operacional a reservar um núcleo só para ele. Uma sincronização bem feita permite o compartilhamento de recursos entre threads e a distribuição do processamento entre núcleos, mas isso não é fácil de se fazer. A melhor forma de atingir esse objetivo é através do isolamento de recursos compartilhados em outro processo (como um banco de dados) que faz a sincronização, enquanto as threads rodam totalmente independente umas das outras.
Espero que eu tenha conseguido dar alguma dica proveitosa. Abraços!
P.S.: Eu programei muitos, muitos anos, para Windows, e não me lembro de ter problemas com distribuição de processos entre núcleos de processadores. Acho interessante sermos isentos quanto às nossas posições em relação a outros sistemas ou produtos porque eles podem ter coisas muito boas que podemos usar como modelo para nossos próprios trabalhos. Mesmo o Windows pode ter alguma coisa para contribuir para a gente.
[11] Comentário enviado por stremer em 26/08/2010 - 23:34h
psfdeveloper....
falou tudo... era nisso que queria chegar, mas realmente tenho uma dificuldade impressionante para tentar explicar algumas coisas...
agora vc fez lembrar do cooperativo e preemptivo.... no dos era cooperativo qdo se implementava em aplicativos win31 por exemplo....
qto ao windows... depende mesmo dos recursos compartilhados, por isso as vezes nao usa os nucleos mesmo programando em 2 ou mais threads...
[12] Comentário enviado por psfdeveloper em 27/08/2010 - 06:55h
Muito obrigado pelo comentário, stremer.
Eu me lembro do Win31 e do DOS, também, afinal, foi nesses sistemas que eu comecei a programar. Quando o Windows95 veio com a multitarefa preemptiva, eu lembro que quase chorei de tanto que fiquei fascinado com a idéia. Hoje, multitarefa preemptiva existe até em sistemas operacionais embarcados e em telefones celulares (como no caso do Symbian, por exemplo).
Multi-threading é um assunto tão fascinante que os iniciantes, quando começam a trabalhar com elas, acham que se trata de uma panacéia e que tudo pode ser feito com Threads. Eu mesmo passei por isso. Teve uma época que eu queria fazer tudo com multitarefa. Mas aí vieram os compartilhamentos de recursos, os locks, as operações ilegais (no windows) os segmentation faults e eu percebi que programar multitarefa é espinhoso.
Linguagens como Python e, também o Java, tornam a criação de código multi-tarefa quase trivial - o que é simplesmente maravilhoso! Quando eu comecei, tínhamos três opções: Pascal, C e Assembly. Nem mesmo os compiladores C++ eram bons o suficiente - e não existiam bibliotecas decentes para multitarefa. Eu me lembro do compilador da Microsoft sem suporte a Templates (!) e com uma infinidade de bugs, ainda por cima.
Mas devemos lembrar sempre: um programa mono-tarefa bem feito, como, por exemplo, uma máquina de estados para controle de eventos, pode ser até mais eficiente, econômico em recursos de sistema e fácil de ler e entender do que a sua contrapartida multitarefa. Devemos lembrar: threads são um assunto avançado e merecem respeito. O Python torna fácil manipular as Threads, mas ele não faz com que as threads se comportem como queremos: ainda estamos no controle. Afinal, os engenheiros, analistas ou programadores somos nós, não a linguagem.
Threads são maravilhosas, mas devem ser usadas criteriosamente - e com parcimônia!
[13] Comentário enviado por stremer em 27/08/2010 - 10:24h
exato... não é necessário usar thread para implementar tarefas em paralelo.
Threads são complexas embora aparentemente simples. Seu uso sem critério pode ter graves consequências, como por exemplo saturação de recursos do sistema.
Se fosse tão simples, "roda em paralelo, cria-se thread", não existiria thread pool (apenas um exemplo).
E como sempre, analise caso a caso antes de sair implementando determinada solução.
Mesmo assim os exemplos do thread no python estão legais.