A técnica de criação de threads com o uso da biblioteca de threads Windows é semelhante à técnica do Pthreads em vários aspectos. Ilustramos a API de threads Windows no programa em C mostrado na Figura 4.11. Observe que devemos incluir o arquivo de cabeçalho windows.h ao usar a API Windows.
Como na versão do Pthreads mostrada na Figura 4.9, os dados compartilhados por threads separados − nesse caso, Sum − são declarados globalmente (o tipo de dado DWORD é um inteiro de 32 bits sem sinal). Também definimos a função Summation ( ) que deve ser executada em um thread separado. Essa função recebe um ponteiro para um tipo void, que o Windows define como LPVOID. O thread que executa essa função define a soma de dados global Sum como o resultado da soma dos valores de 0 até o parâmetro passado para Summation ( ).
#define NUM_THREADS 10
/* um array de threads para o agrupamento */ pthread_t workers[NUM THREADS];
for (int i = 0; i < NUM_THREADS; i++) pthread_join(workers[i], NULL);
Figura 4.10 Código Pthread para o agrupamento de dez threads.
Os threads são criados na API Windows com o uso da função CreateThread ( ), e — como no Pthreads — um conjunto de atributos do thread é passado para essa função. Esses atributos incluem informações de segurança, o tamanho da pilha e um flag que pode ser posicionado para indicar se o thread deve ser iniciado em estado de suspensão. Nesse programa, usamos os valores default desses atributos. (Inicialmente, os valores default não estabelecem um estado de suspensão para o thread e, em vez disso, o tornam elegível para ser executado pelo scheduler da CPU.) Uma vez que o thread de soma seja criado, o pai deve esperar que ele seja concluído antes de exibir o valor de Sum, já que o valor é definido pelo thread de soma. Lembrese de que, no programa Pthread (Figura 4.9), o threadpai espera pelo thread de soma usando o comando pthread_join ( ). Fizemos algo semelhante na API Windows usando a função WaitForSingleObject ( ), que causa o bloqueio do thread criador até que o thread de soma tenha sido encerrado.
Em situações que requerem a espera da conclusão de múltiplos threads, a função WaitForMultipleObjects ( ) é usada. Essa função recebe quatro parâmetros: O número de objetos que estão sendo esperados Um ponteiro para o array de objetos Um flag indicando se todos os objetos foram sinalizados Um tempo limite (ou INFINITE) Por exemplo, se THandles é um array de objetos thread HANDLE de tamanho N, o threadpai pode esperar que todos os seus threadsfilhos sejam concluídos com esse comando:
WaitForMultipleObjects (N, THandles, TRUE, INFINITE)
Threads Java
Os threads são o modelo básico de execução de programas em Java, e a linguagem Java e sua API fornecem um rico conjunto de recursos para a criação e o gerenciamento de threads. Todos os programas em Java são compostos por, pelo menos, um thread de controle — até mesmo um programa em Java simples, composto por apenas um método main ( ), é executado como um único thread na JVM. Os threads Java estão disponíveis em qualquer sistema que forneça uma JVM, incluindo o Windows, o Linux e o MacOS X. A API de threads Java também está disponível para aplicações do Android.
Há duas técnicas para a criação de threads num programa em Java. Uma abordagem é a criação de uma nova classe derivada da classe Thread e a sobreposição de seu método run ( ). Uma técnica alternativa — e mais usada — é a definição de uma classe que implementa a interface Runnable. A interface Runnable é definida como descrito a seguir:
public interface Runnable {
public abstract void run (); }
Quando uma classe implementa Runnable, ela deve definir um método run ( ). O código que implementa o método run ( ) é o que é executado como um thread separado.
#include <windows.h> #include <stdio.h>
DWORD Sum; /* os dados são compartilhados pelo(s) thread(s) */ /* o thread é executado nessa função separada */
DWORD WINAPI Summation(LPVOID Param) {
DWORD Upper = *(DWORD*)Param; for (DWORD i = 0; i <= Upper; i++) Sum += i;
return 0; }
int main(int argc, char *argv[]) {
DWORD ThreadId; HANDLE ThreadHandle; int Param;
if (argc != 2) {
fprintf(stderr,”An integer parameter is required\n”); return -1;
}
Param = atoi(argv[1]); if (Param < 0) {
fprintf(stderr,”An integer >= 0 is required\n”); return -1;
}
/* cria o thread*/
ThreadHandle = CreateThread(
NULL, /* atributos default de segurança */ 0, /* tamanho default de pilha */
Summation, /* função do thread */
1. 2.
0, /* flags default de criação */
&ThreadId); /* retorna o identificador do thread */ if (ThreadHandle != NULL) {
/* agora espera que o thread seja encerrado */ WaitForSingleObject(ThreadHandle,INFINITE); /* fecha o manipulador do thread */
CloseHandle(ThreadHandle); printf(“sum = %d\n”,Sum); }
}
Figura 4.11 Programa em C com múltiplos threads usando a API Windows.
A Figura 4.12 mostra a versão Java de um programa com múltiplos threads que determina a soma de inteiros não negativos. A classe Summation implementa a interface Runnable. A criação de threads é executada com a criação de uma instância de objeto da classe Thread e a passagem de um objeto Runnable para o construtor.
A criação de um objeto Thread não cria, especificamente, o novo thread; em vez disso, é o método start ( ) que cria o novo thread. A chamada do método start ( ) do novo objeto faz duas coisas:
Aloca memória e inicializa um novo thread na JVM.
Chama o método run ( ), tornando o thread elegível para execução na JVM. (Observe, mais uma vez, que nunca chamamos o método run ( ) diretamente. Em vez disso, chamamos o método start ( ) que chama o método run ( ) em nosso nome.)
Quando o programa de soma é executado, a JVM cria dois threads. O primeiro é o threadpai, que começa a execução no método main ( ). O segundo thread é criado quando o método start ( ) é invocado no objeto Thread. Esse threadfilho começa a execução no método run ( ) da classe Summation. Após dar saída no valor da soma, o thread termina ao sair de seu método run ( ).
O compartilhamento de dados entre threads ocorre facilmente no Windows e no Pthreads porque os dados compartilhados são simplesmente declarados como globais. Já que é totalmente orientada a objetos, a linguagem Java não tem o conceito de dados globais. Se dois ou mais threads precisam compartilhar dados em um programa em Java, o compartilhamento ocorre pela passagem de referências do objeto compartilhado para os threads apropriados. No programa em Java mostrado na Figura 4.12, o thread principal e o thread de soma compartilham a instância de objeto da classe Sum. Esse objeto compartilhado é referenciado pelos métodos getSum ( ) e setSum ( ) apropriados. (Você deve estar se perguntando por que não usamos um objeto Integer em vez de projetar uma nova classe Sum. O motivo é que a classe Integer é imutável — isto é, uma vez que seu valor seja estabelecido, ele não pode mudar.)
Você deve lembrar que os threadspais das bibliotecas Pthreads e Windows usam pthread_join ( ) e WaitForSingleObject ( ) (respectivamente) para esperar que os threads de soma terminem antes de prosseguir. O método join ( ) em Java fornece funcionalidade semelhante. (Observe que join ( ) pode lançar uma InterruptedException, que preferimos ignorar.) Se o pai deve esperar que múltiplos threads terminem, o método join ( ) pode ser inserido em um loop for semelhante ao mostrado para o Pthreads na Figura 4.10.
class Sum {
private int sum; public int getSum() {
return sum; }
public void setSum(int sum) { this.sum = sum;
} }
class Summation implements Runnable {
private int upper; private Sum sumValue;
public Summation(int upper, Sum sumValue) { this.upper = upper;
this.sumValue = sumValue; }
public void run() { int sum = 0;
for (int i = 0; i <= upper; i++) sum += i;
sumValue.setSum(sum); }
}
public class Driver {
public static void main(String[] args) { if (args.length > 0) {
if (Integer.parseInt(args[0]) < 0)
System.err.println(args[0] + “ must be >= 0.”); else {
Sum sumObject = new Sum();
int upper = Integer.parseInt(args[0]);
Thread thrd = new Thread(new Summation(upper, sumObject)); thrd.start();
try {
thrd.join();
System.out.println
(“The sum of “+upper+” is “+sumObject.getSum()); } catch (InterruptedException ie) { }
} } else
System.err.println(“Usage: Summation <integer value>”); } }
Figura 4.12 Programa em Java para a soma de inteiros não negativos.
4.5
4.5.1
1. 2.
A JVM costuma ser implementada no topo de um sistema operacional hospedeiro (consulte a Figura 16.10). Essa configuração permite que a JVM oculte os detalhes de implementação do sistema operacional subjacente e forneça um ambiente abstrato e consistente que permita aos programas Java operarem em qualquer plataforma que suporte a JVM. A especificação da JVM não indica como os threads Java devem ser mapeados para o sistema operacional subjacente, deixando essa decisão a cargo da implementação específica da JVM. Por exemplo, o sistema operacional Windows XP usa o modelo umparaum; assim, cada thread Java de uma JVM, sendo executada nesse sistema, é mapeado para um thread do kernel. Em sistemas operacionais que usam o modelo muitospara muitos (como o Tru64 UNIX), um thread Java é mapeado de acordo com tal modelo. Inicialmente, o Solaris implementava a JVM usando o modelo muitosparaum (a biblioteca green threads, mencionada anteriormente). Versões posteriores da JVM foram implementadas usando o modelo muitosparamuitos. A partir do Solaris 9, os threads Java começaram a ser mapeados com o uso do modelo umparaum. Além disso, pode haver um relacionamento entre a biblioteca de threads Java e a biblioteca de threads do sistema operacional hospedeiro. Por exemplo, implementações de uma JVM para a família de sistemas operacionais Windows podem usar a API Windows ao criar threads Java; os sistemas Linux, Solaris e Mac OS X podem usar a API Pthreads.