EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Introdução aos Sistemas Operacionais Threads Eleri Cardozo FEEC/Unicamp EA876 - Prof. Eleri Cardozo - FEEC/Unicamp O que é uma Thread? Uma thread (linha de controle) é uma unidade de execução e de controle, alocação e compartilhamento de recursos interna ao processo. Processo Threads Contexto (threads) Contexto (processo) EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Motivações para Threads Minimizar a troca de contexto causada por processos que bloqueiam. ● ● Facilitar a implementação de programas concorrentes: - Grau mais fino de concorrência. - Facilidade de comunicação e sincronização inter-threads. - Troca de contexto mais rápida que processos (e sem influênciar o processo de paginação). - Criação e finalização mais rápida que processos (idem). EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Modelos de Threads Modelos de threads ditam como as threads se relacionam com o núcleo do sistema operacional. Quando o núcleo do sistema operacional não provê suporte a threads, é possível implementar threads no espaço do usuário por meio de bibliotecas específicas (por exemplo, pth - GNU Portable Threads). Este cenário é raro na atualidade. Quando o núcleo do sistema operacional provê suporte a threads, é possível utilizar threads por meio de chamadas de sistema específicas (por exemplo, pthreads - POSIX Threads). Este cenário é o mais comum na atualidade. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Modelos de Threads Problemas com threads no espaço do usuário: Apenas uma thread executa por vez (não tira proveito de processadores multicore). ● Não é possível interromper a execução de uma thread (a biblioteca de threads não pode definir um manipulador de interrupção de relógio !) ● Todas as chamadas bloqueantes devem ser redefinidas pela biblioteca de threads (pth define apenas um subconjunto). ● EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Exemplo: POSIX Threads #include <pthread.h> // definicao de thread void* thread1(void *p) { printf("Thread recebeu parametro: %s\n", (char *)p); doWork(p); } // programa principal main() { pthread_t pth; pthread_create(&pth, NULL, thread1, "Alo thread"); pthread_join(pth, NULL); // espera thread terminar } EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Comunicação Inter-threads Como threads compartilham a área de dados do processo, a comunicação inter-threads por memória compartilhada é a mais natural. Este mecanismo não requer a intermediação do sistema operacional, exceto para fins de sincronização. Pilha área compartilhada Dados Thread 1 Texto Thread 2 EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Sincronização Inter-threads Mutexes: semáforos binários (inicializados em 1). Variáveis de condição: permitem o compartilhamento de mutexes com o objetivo de evitar espera ocupada (polling). Tal como os mecanismos de sincronização inter-processo, mutexes e variáveis de condição devem ser providos pelo núcleo do sistema operacional. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Sincronização Inter-threads Exemplo do uso de variáveis de condição: problema produtor-consumidor. Thread mestre (produtora) Mutex Buffer ? Variável de condição Threads trabalhadoras (consumidoras) EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Sincronização Inter-threads Implementação (ineficiente !) das threads consumidoras: #include <pthread.h> pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; while(1) { // espera ocupada pthread_mutex_lock(&m); if(buffer.size == 0) { pthread_mutex_unlock(&m); usleep(10000); } else break; } // do work pthread_mutex_unlock(&m); EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Sincronização Inter-threads Implementação (eficiente !) das threads consumidoras: #include <pthread.h> pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&m); if(buffer.size == 0) pthread_cond_wait(&c, &m); // do work pthread_mutex_unlock(&m); EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Sincronização Inter-threads Implementação (eficiente !) da thread produtora: #include <pthread.h> pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&m); // do work pthread_cond_signal(&c, &m); pthread_mutex_unlock(&m); EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Escalonamento de Threads O escalonamento de threads é idêntico ao de processos, ou seja, cada thread tem seu contexto salvo/restaurado quando a thread perde/recupera a CPU. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Escalonamento de Threads Escalonamento não preemptivo (threads no nível do usuário): threads executam até bloquear ou terminar. Escalonamento preemptivo (threads no nível do núcleo): threads são escalonadas por time-sharing ou por prioridades. Tal como processos, Linux suporta escalonamento de threads por prioridades tipo FIFO ou RR (Round Robin). Os sistemas operacionais atuais escalonam threads, ou seja, o processo perde a CPU quando expira o quantum de CPU ou quando não existe nenhuma se suas threads no estado de pronto. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Escalonamento de Threads #include <pthread.h> main() { pthread_t pth; pthread_attr_t ta; // atributos de escalonamento struct sched_param sp; // pars. de escalonamento // obtem a maxima prioridade que uma thread pode ter sp.sched_priority = sched_get_priority_max(SCHED_FIFO); // define parametros de escalonameento pthread_attr_init(&sp); pthread_attr_setinheritsched(&ta, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&ta, SCHED_FIFO); pthread_attr_setschedparam(&ta, &sp); // cria thread com prioridade explicita pthread_create(&pth, &ta, thread1, "Alo thread"); pthread_join(pth, NULL); // espera thread terminar } EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Threads e a Chamada fork O que acontece quando um programa que iniciou múltiplas threads executa a chamada fork? (a) O processo filho inicia com todas as threads em execução no processo pai. (b) O processo filho é criado apenas com a thread principal (função main). No Unix ocorre a alternativa (b). Pior ainda, os mutexes e as variáveis de condição no processo filho assumem um estado indefinido. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Threads em Sistemas de Tempo Real Em sistemas de tempo real deve-se evitar a criação e destruição frequente de threads durante a execução do sistema (o overhead é baixo mas não desprezível). É preferível a criação de um pool de threads na inicializaçao e, eventualmente, um ajuste do tamanho do pool durante a execução do sistema. As threads do pool são sincronizadas com mutexes e variáveis de condição. EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Exemplo: Servidor Multithreaded EA876 - Prof. Eleri Cardozo - FEEC/Unicamp Atividades Práticas A biblioteca pthread Criação de threads Mutexes Variáveis de condição