Linguagem de Programação IV Carlos Oberdan Rolim Ciência da Computação Sistemas de Informação Threads O que são Threads? Enquanto um processo é um meio de agrupar recursos, threads são as entidades escalonadas para a execução sobre a CPU. Acrescentam ao modelo de processo a possibilidade que múltiplas execuções ocorram no mesmo ambiente de um processo e com um grande grau de independência uma da outra. Processos e Threads Recursos X escalonamento/execução Características essenciais de um processo são inseparáveis e tratadas em conjunto pelo S.O. Características independentes Podem ser tratadas independentemente pelo S.O. Unidade de escalonamento é a thread (lightweight process) Unidade de posse de recursos é o processo/tarefa Threads Threads também são chamados de lightweight process ou processos leves. O termo Multithread também é usado para descrever a situação em que se permite a existência de múltiplos threads no mesmo processo. Multithreading Capacidade do Sistema Operacional suportar múltiplas threads de execução dentro de um único processo Single-Thread x Multithread um processo um processo uma thread várias threads vários processos vários processos uma thread por processo várias threads por processo Funcionamento de uma Thread Threads distintos em um processo não são tão independentes quanto processos distintos, todos os threads tem exatamente o mesmo espaço de endereçamento. Ou seja, isso significa que eles também compartilham as mesmas variáveis globais. Funcionamento de uma Thread Cada thread pode ter acesso a qualquer endereço de memória dentro do espaço de endereçamento do processo. Um thread pode ler, escrever ou até mesmo apagar os dados de outra thread. É impossível um processos distintos acessar aos dados de outro processo. Modelo de Processo (single thread) Espaço de endereçamento para armazenamento da imagem do processo (espaço de usuário e de sistema) Informações de recursos alocados são mantidos no descritor de processos Contexto de execução (pilha, programa, dados, ...) processo Espaço de usuário Espaço de sistema SP pilha PC código dados dados pilha Vários processos Um fluxo de controle por processo (thread) Troca do processo implica em atualizar estruturas de dados internas do S.O. Contexto, espaço de endereçamento, . . . Espaço de usuário Espaço de sistema processo SP processo pilha PC código dados dados pilha SP pilha PC código dados dados pilha Vários fluxos em um único processo Um fluxo de instrução é implementado através do PC e SP Estruturas comuns compartilhadas Código Dados Descritor de processos conceito de thread processo Espaço de usuário Espaço de sistema SP1 PC1 SP2 pilha PC2código dados PC3 SP3 dados pilha Multiprogramação Pesada Custo do gerenciamento de processos criação do processo troca de contextos esquemas de proteção, memória virtual, etc... Custos são fator limitante na interação de processos unidade de manipulação é o processo Mecanismos de IPC necessitam a manipulação de estruturas complexas que representam o processo e suas propriedades (através de chamadas de sistema) Solução “Aliviar” os custos, reduzir o overhead envolvido Multiprogramação Leve Fornecido pela abstração de um fluxo de execução (thread) Unidade de interação passa a ser função Contexto de uma thread Registradores(pilha, apontador de programa, registradores de uso geral) Comunicação por memória compartilhada Por que utilizar threads? Permitir a exploração do paralelismo real oferecido por máquinas multiprocessadas Aumentar o número de atividades executadas por unidade de tempo (throughput) Esconder latência do tempo de resposta possibilidade de associar threads a dispositivos de I/O Sobrepor operações de cálculo com operações de I/O Estados de uma Thread Estados fundamentais: Executando Pronta Bloqueada Vantagens do Multithreading Tempo de criação/destruição de threads é inferior que tempo de criação/destruição de processos Chaveamento de contexto entre threads é mais rápido que tempo de chaveamento entre processos Como threads compartilham o descritor do processo, elas dividem o mesmo espaço de endereçamento o que permite a comunicação por memória compartilhada sem interação com o núcleo (kernel) do S.O. Segurança de Threads Não há proteção entre as threads. Porque? É impossível Não é necessário (Todas as threads presumidamente são criadas pelo mesmo usuário) Threads foram criadas para cooperar e não competir Em que nível implementar? Nível do usuário Gerenciamento dos Threads é feito pela aplicação. Escalonamento é feito pela aplicação Threads x Processos Thread Processo _ real user 17.4 3.9 sys _ real 13.5 5.9 user sys INTEL 2.2 GHz Xeon 2 CPU/node 2 GB Memory RedHat Linux 7.3 0.8 5.3 Exemplo - RPC Exemplo - RPC Exemplo I/O preempt Thread A (proc. 1) Thread B (proc. 1) Thread C (proc. 2) preempt criação processo novo bloqueado pronto em execução t Implementação de Threads Threads são implementadas através de estrutura de dados similares ao descritor de processo Descritor de threads Menos complexa (leve) Podem ser implementadas em dois níveis diferentes espaço de usuário (user level threads) espaço de sistema (kernel level threads) Single Threaded x Multithreaded multithreaded thread thread thread thread thread thread block block block process user user user control stack stack stack single threaded process control user block stack user kernel block address stack user kernel kernel kernel address stack stack stack space space Modelo 1:1 Threads a nível de usuário user level threads ou process scope Todas as tarefas de gerenciamento de threads é feito a nível da aplicação threads são implementadas por uma biblioteca que é ligada ao programa Interface de programação (API) para funções relacionadas com threads Ex: criação, sincronismo, término, etc ... O sistema operacional não “enxerga” a presença das theads A troca de contexto entre threads é feita em modo usuário pelo escalonador embutido na biblioteca não necessita privilégios especiais escalonamento depende da implementação Implementação do modelo 1:1 PC1 SP1 Espaço PC2 SP2 de usuário PCn SP3 processo SP escalonador pilha PC código dados biblioteca CPU virtual biblioteca Espaço de dados pilha escalonador sistema operacional sistema CPU Modelo 1:1 Vantagens Sistema operacional divide o tempo do processador entre os processos “ pesados” e, a biblioteca de threads divide o tempo do processo entre as threads Leve: sem interação/intervenção do S.O. Desvantagens Uma thread que realiza uma chamada de sistema bloqueante bloqueia todo o processo e suas threads Não explora o paralelismo das máquinas multiprocessadas Modelo N:1 Threads a nível de sistema kernel level threads ou system scope Resolve desvantagens do modelo 1:1 O sistema operacional “enxerga” as threads S.O. mantém informações sobre processos e sobre threads troca de contexto necessita a intervenção do S.O. O conceito de threads é considerado na implementação do S.O. Implementação do modelo N:1 PC1 SP1 Espaço PC2 SP2 de usuário Espaço de PCn SP3 dados processo SP pilha PC código dados CPU CPU virtual virtual pilha CPU virtual escalonador sistema operacional sistema CPU Modelo N:1 Vantagens Explora paralelismo da máquinas multiprocessadas (SMP) Facilita o recobrimento de operações de operações de I/O por cálculos Desvantagens Implementação “mais pesada” que o modelo 1:1 Modelo 1:N Permite que uma thread migre de um processo para outro. Isto permite a movimentação da thread entre sistemas distintos Modelo M:N Abordagem que combina os modelos N:1 e 1:N PTHREADS IEEE POSIX 1003.1c standard (1995). Implementações que aderem a este padrão são denominados como POSIX threads ou Pthreads Definido como um conjunto de tipos e chamadas de procedimentos da linguagem C, implementada com um arquivo “header/include” pthread.h e uma biblioteca de threads Projeto de Programas com Threads Para que um programa tenha um melhor desempenho com threads ele precisa ser organizado em tarefas distintas e independentes que podem ser executadas concorrentemente Por exemplo, se tivermos dois ou mais procedimentos que podem ser trocados de ordem de execução, intercalados ou sobrepostos em tempo real, então eles são candidatos a serem implementados em threads distintas Projeto de Programas com Threads Procedimento 1 Procedimento 2 Procedimento 2 P2 P1 P2 P1 Proc Final Procedimento 1 P2 P1 Procedimento 2 Procedimento 1 P2 P1 Proc Final Proc Final Proc Final Tarefas candidatas a serem implementadas em threads distintas Tarefas que Têm um potencial de ficarem bloqueadas por um longo tempo Usam muitos ciclos de CPU Que devem responder a eventos assíncronos Que tenham uma importância menor ou maior do que outras tarefas Que são aptas de serem executadas em paralelo com outras tarefas API Pthread Podem ser grupadas em três classes Gerenciamento de threads Criar, destruir, atachar, etc... threads Mutexes Criar, destruir, trancar e destrancar mutexes Variáveis de condição Funções de comunicação entre threads que têm mutexes em comum. Funções para criar, destruir, wait e signal, set e query de variáveis de condição Convenção de nomes Routine Prefix pthread_ pthread_attr_ pthread_mutex_ pthread_mutexattr_ pthread_cond_ pthread_condattr_ pthread_key_ Functional Group Threads and miscellaneous subroutines Thread attributes objects Mutexes Mutex attributes objects. Condition variables Condition attributes objects Thread-specific data keys Gerenciamento de threads •Criar threads •pthread_create (thread,attr,start_routine,arg) •pthread_exit (status) •pthread_attr_init (attr) •pthread_attr_destroy (attr) Criando e terminando threads Criar threads pthread_create (thread,attr,start_routine,arg) Finalizar threads pthread_exit (status) Definir atributos pthread_attr_init (attr) Remover atributos pthread_attr_destroy (attr) Criando threads •main() cria uma thread. As outras devem ser criadas pelo programador •pthread_create: cria uma nova thread (pode ser chamada varias vezes) •Argumentos da função: •thread: identificador para nova thread que será retornado pela rotina •attr: conjunto de atributos para definição das threads que serão criadas. NULL para valores default •start_routine: a função que será executada assim que a thread for criada •arg: Um argumento que será passado para a função. Ele deve ser passado por referencia com um ponteiro do tipo void (casting). NULL pode ser utilizado para não passar argumentos •O numero maximo de threads que podem ser criadas depende da implementação •Uma vez criadas as threads são “irmãs”. Não existe o conceito de hierarquia ou dependencia entre threads. Terminando threads Threads podem ser terminadas de varias formas. A thread volta para a rotina que a criou (função main para thread inicial) A thread faz uma chamada para a função pthread_exit() A thread é cancelada por outra thread através da função pthread_cancel() O processo inteiro é terminado #include <pthread.h> #include <stdio.h> #define NUM_THREADS 5 Exemplo de criação e Destruição de Thread void *PrintHello(void *threadid){ printf("\n%d: Hello World!\n", threadid); pthread_exit(NULL); } int main(int argc, char *argv[]){ pthread_t threads[NUM_THREADS]; } int rc, t; for(t=0;t<NUM_THREADS;t++){ printf("Creating thread %d\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(NULL); % gcc –o thread thread.c –lpthread % ./thread Passagem de parâmetros A melhor maneira de passar parametros é atraves do uso de uma estrutura Cada thread recebe uma instância da estrutura #include <pthread.h> #include <stdio.h> struct thread_data{ int thread_id; int sum; char *message; }; Exemplo de criação com passagem de parametros para as threads usando estrutura struct thread_data thread_data_array[NUM_THREADS]; void *PrintHello(void *threadarg){ struct thread_data *my_data; ... my_data = (struct thread_data *) threadarg; taskid = my_data->thread_id; sum = my_data->sum; hello_msg = my_data->message; ... } int main (int argc, char *argv[]){ ... thread_data_array[t].thread_id = t; thread_data_array[t].sum = sum; thread_data_array[t].message = messages[t]; rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &thread_data_array[t]); ... } União e separação de threads (join and detach) União pthread_join (threadid,status) Separação pthread_detach (threadid) Definição de atributos pthread_attr_setdetachstate (attr,detachstate) pthread_attr_getdetachstate (attr,detachstate) União (join) Mecanismo de sincronização de threads Pode ser usado mutexes e condições pthread_join() bloqueia a função chamadora até que a thread threadid termine O programador pode obter o status do termino de uma thread se ele for especificado em pthread_exit(). Uma união pode esperar somente um pthread_join(). É um erro lógico esperar por multiplos joins na mesma thread pthread_detach() pode ser usado para explicitamente detach uma thread mesmo que ela tenha sido criada como joinable. Joinable or Not? Quando uma thread é criada um de seus atributos é a definição se uma thread é joinable ou não. Somente threads criadas como joinable podem ser unidas (joined). Threads criadas como detachabled nunca podem ser unidas O padrão POSIX diz que threads tem de ser criadas como joinable Para explicitar uma thread como joinable deve ser definido seu atributo na criação Criar atributo do tipo pthread_attr_t pthread_attr_t attr; Inicializar os atributos pthread_attr_init() Definir o status de detach pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Quando terminar, liberar recursos usados pelos atributos pthread_attr_destroy() Joinable or joining if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #define NUM_THREADS 4 void *BusyWork(void *t){ int i; long tid; double result=0.0; tid = (long)t; printf("Thread %ld starting...\n",tid); for (i=0; i<1000000; i++) result = result + sin(i) * tan(i); /* Free attribute and wait for the other threads */ pthread_attr_destroy(&attr); for(t=0; t<NUM_THREADS; t++) { rc = pthread_join(thread[t], &status); if (rc) { printf("ERROR; return code from pthread_join() is %d\n", rc); exit(-1); } printf("Thread %ld done. Result = %e\n",tid, result); pthread_exit((void*) t); printf("Main: completed join with thread %ld having a status of %ld\n",t,(long)status); } } int main (int argc, char *argv[]){ pthread_t thread[NUM_THREADS]; pthread_attr_t attr; int rc; long t; void *status; /* Initialize and set thread detached attribute */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(t=0; t<NUM_THREADS; t++) { printf("Main: creating thread %ld\n", t); rc = pthread_create(&thread[t], &attr, BusyWork, (void *)t); printf("Main: program completed. Exiting.\n"); pthread_exit(NULL); } Saída: Main: creating thread 0 Main: creating thread 1 Thread 0 starting… Main: creating thread 2 Thread 1 starting… Main: creating thread 3 Thread 2 starting… Thread 3 starting… Thread 1 done. Result = -3.153838e+06 Thread 0 done. Result = -3.153838e+06 …. Main: program completed. Exiting. Threads e Mutexes Mutexes são usados para controlar acesso a variaveis compartilhadas Variáveis mutexes precisam ser declaradas pthread_mutex_t Criar e destruir mutexes pthread_mutex_init (mutex,attr) pthread_mutex_destroy (mutex) Definir e destribuir atributos de mutexes pthread_mutexattr_init (attr) pthread_mutexattr_destroy (attr) Lock e unlock pthread_mutex_lock (pthread_mutex_t *mut); pthread_mutex_unlock (pthread_mutex_t *mut); #include <stdio.h> #include <stdlib.h> #include <pthread.h> void *functionC(); pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; int counter = 0; main(){ int rc1, rc2; pthread_t thread1, thread2; /* Create independent threads each of which will execute functionC */ if( (rc1=pthread_create( &thread1, NULL, &functionC, NULL)) ){ printf("Thread creation failed: %d\n", rc1); } if( (rc2=pthread_create( &thread2, NULL, &functionC, NULL)) ){ printf("Thread creation failed: %d\n", rc2); } /* Wait till threads are complete before main continues. Unless we */ /* wait we run the risk of executing an exit which will terminate */ /* the process and all threads before the threads have completed. */ pthread_join( thread1, NULL); pthread_join( thread2, NULL); exit(0); } void *functionC() { pthread_mutex_lock( &mutex1 ); counter++; printf("Counter value: %d\n",counter); pthread_mutex_unlock( &mutex1 ); }