EEL770 Sistemas Operacionais Parte 2: Concorrência Conceitos básicos e ciclo de vida de threads Prof. Rodrigo de Souza Couto
Concorrência Múltiplas atividades que podem ocorrer ao mesmo tempo Exemplos Um servidor de grande porte recebe requisições de milhares de usuários ao mesmo tempo Uma aplicação de cálculo científico divide o processamento em diversas tarefas Programar software concorrente requer cuidados e conhecimento de Sistemas Operacionais
Threads Uma thread é uma unidade básica de utilização da CPU ID de thread Contador de programa (PC) Um conjunto de registradores Pilha de execução Nas aulas anteriores, um processo tinha única thread Mas um processo pode ter várias threads Threads de um mesmo processo compartilham Seção de código Seção de dados Arquivos abertos Outros recursos do S.O. como arquivos abertos e sinais 3
Processo com uma única thread Código Dados Arquivos Registradores Pilha thread Figura adaptada de [4] 4
Processo com múltiplas threads Código Dados Arquivos Registradores Registradores Registradores Pilha Pilha Pilha thread thread thread Figura adaptada de [4] 5
Exemplo de uso de threads Exibição de mapa, com diferentes tarefas concorrentes Figura retirada de [1] 6
SO como ilusionista Abstração de infinitas CPUs Figura retirada de [1] 7
Não seria possível fazer software concorrente só com processos? 8
Não seria possível fazer software concorrente só com processos? Possível é, mas poderia implicar em perda de desempenho Threads compartilham espaço de endereçamento Útil quando tarefas concorrentes utilizam os mesmo dados Não é necessário utilizar técnicas de IPC Threads são mais rápidas de criar e destruir P.ex., não é necessária a cópia de dados exigida pelo fork Em alguns livros, threads são referidas como processos leves Processos podem ser vistos como uma forma de proteger uma thread ou um conjunto de threads 9
Quando usar threads? Para expressar tarefas concorrentes Alguns programas são naturalmente concorrentes P.ex., servidor web, aplicação de mapa vista anteriormente, etc. Para prover responsividade Deixar tarefas executando em segundo plano enquanto outras podem receber entradas do usuário P.ex., interface gráfica, na qual botões como "cancelar" devem estar sempre disponíveis 10
Quando usar threads? Para aumento de desempenho, em arquiteturas com múltiplas CPUs Cada thread pode ser executada por uma CPU diferente Para aumento de desempenho, em aplicações com tarefas de E/S Enquanto uma thread está bloqueada por E/S, a outra está executando o processamento Ganho de desempenho até em arquiteturas com uma CPU 11
Exemplo de quantidade de threads no Windows 10 Para fazer no seu, use a ferramenta https://docs.microsoft.com/pt-br/sysinternals/downloads/process-explorer 12
Abstração de Threads Uma thread é um sequência única de execução que representa uma tarefa escalonável separadamente Cada thread pode ser vista como uma programa sequencial S.O. pode executar ou suspender a thread a qualquer momento Cada thread executa em uma CPU virtual, com velocidade imprevisível e variável Ou seja, thread pode levar mais tempo ou menos tempo para executar, dependendo do escalonamento de outras threads 13
Exemplo de execução de thread Figura retirada de [1] 14
Exemplo de execução de thread Figura retirada de [1] 15
API para threads baseada no padrão POSIX void pthread_create(thread,func,arg) Cria uma thread, armazenando sua referência no argumento thread Thread executa função func, que recebe os argumento de arg Análogo à combinação fork + exec para processos Chamada assíncrona Programa que chamou não espera retorno da função para prosseguir 16
API para threads baseada no padrão POSIX pthread_join(thread) Espera a finalização da thread referenciada por thread pthread_yield() A thread que chamar essa função pode desistir de sua vez na CPU, em favor de alguma outra thread Vai para o final da fila de threads prontas Multi-threading cooperativo void pthread_exit(ret) Finaliza a thread que chamou e coloca em ret uma referência para o valor de retorno 17
Exemplo de uso da thread Programa criará diversas threads para chamar a função go Função receberá o número da thread, imprimirá na tela esse número, e retornará o número mais 100 void * go(void *n){ int *ret = (int *)n; printf("hello from thread %d\n", *ret); *ret += 100; pthread_exit(ret); Adaptado de [1] 18
Exemplo de uso da thread Programa criará diversas threads para chamar a função go Função receberá o número da thread, imprimirá na tela esse número, e retornará o número mais 100 Função declarada como ponteiro Recebe ponteiro para void. Permite função receber qualquer estrutura de void * go(void *n){ dados int *ret = (int *)n; printf("hello from thread %d\n", *ret); *ret += 100; pthread_exit(ret); Adaptado de [1] 19
Exemplo de uso da thread Programa criará diversas threads para chamar a função go Função receberá o número da thread, imprimirá na tela esse número, e retornará o número mais 100 void * go(void *n){ int *ret = (int *)n; printf("hello from thread %d\n", *ret); *ret += 100; pthread_exit(ret); Para esta implementação específica, ponteiro void é transformado em ponteiro para inteiro Finaliza a thread, retornando o ponteiro ret Adaptado de [1] 20
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; Cria 10 threads for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] 21
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] Inicialização de atributos da thread 22
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] Criação da thread 23
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] Espera por cada uma das threads, sequencialmente 24
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] Envia identificador da thread e endereço no qual será armazenado ponteiro de retorno 25
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] Ponteiro para ponteiro deve ser passado como void** 26
#define NTHREADS 10 int main(void) { int inputvalues[nthreads]; static pthread_t threads[nthreads]; pthread_attr_t attributes[nthreads]; int *returnvalues[nthreads]; int i; Vejam o código disponível no site! Reparem que o Makefile precisa chamar -pthread for (i = 0; i < NTHREADS; i++){ pthread_attr_init(&attributes[i]); inputvalues[i] = i; pthread_create(&threads[i],&attributes[i], go, &inputvalues[i]); for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); printf("main thread is done.\n"); return 0; Adaptado de [1] 27
Possível saída do código anterior Hello from thread 1 Hello from thread 0 Hello from thread 7 Hello from thread 2 Hello from thread 4 Hello from thread 5 Hello from thread 6 Hello from thread 8 Hello from thread 3 Hello from thread 9 Thread 0 returned with 100 Thread 1 returned with 101 Thread 2 returned with 102 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread is done. 28
Possível saída do código anterior Hello from thread 1 Hello from thread 0 Hello from thread 7 Hello from thread 2 Hello from thread 4 Hello from thread 5 Hello from thread 6 Hello from thread 8 Hello from thread 3 Hello from thread 9 Thread 0 returned with 100 Thread 1 returned with 101 Thread 2 returned with 102 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread is done. for (i = 0; i < NTHREADS; i++){ pthread_join(threads[i],(void**)&returnvalues[i]); printf("thread %d returned with %d\n", i, *returnvalues[i]); Programa detecta finalização das threads na ordem do seu índice 29
Possível saída do código anterior Hello from thread 1 Hello from thread 0 Hello from thread 7 Hello from thread 2 Hello from thread 4 Hello from thread 5 Hello from thread 6 Hello from thread 8 Hello from thread 3 Hello from thread 9 Thread 0 returned with 100 Thread 1 returned with 101 Thread 2 returned with 102 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread is done. Execução das threads não necessariamente segue a ordem. Por quê? 30
Possível saída do código anterior Hello from thread 1 Hello from thread 0 Hello from thread 7 Hello from thread 2 Hello from thread 4 Hello from thread 5 Hello from thread 6 Hello from thread 8 Hello from thread 3 Hello from thread 9 Thread 0 returned with 100 Thread 1 returned with 101 Thread 2 returned with 102 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread is done. 1-Não há garantia que as threads serão escalonadas na ordem que são criadas 2-Mesmo que seja escalonada na ordem que é criada, thread pode ser interrompida antes de imprimir, dando lugar a outra thread. 31
Uma outra possível saída Hello from thread 0 Hello from thread 1 Hello from thread 2 Hello from thread 3 Hello from thread 4 Hello from thread 5 Hello from thread 6 Thread 0 returned with 100 Hello from thread 8 Hello from thread 9 Thread 1 returned with 101 Thread 2 returned with 102 Hello from thread 7 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread done. Thread 0 finalizou antes mesmo das threads 7,8 e 9 imprimirem 32
Uma outra possível saída Hello from thread 0 Hello from thread 1 Hello from thread 2 Hello from thread 3 Hello from thread 4 Hello from thread 5 Hello from thread 6 Thread 0 returned with 100 Hello from thread 8 Hello from thread 9 Thread 1 returned with 101 Thread 2 returned with 102 Hello from thread 7 Thread 3 returned with 103 Thread 4 returned with 104 Thread 5 returned with 105 Thread 6 returned with 106 Thread 7 returned with 107 Thread 8 returned with 108 Thread 9 returned with 109 Main thread done. Thread 0 finalizou antes mesmo das threads 7,8 e 9 imprimirem 33
Thread Control Block (TCB) Análogo ao PCB para processos Armazenada estado e metadados relacionados a uma thread Um PCB aponta para múltiplos TCBs 34
Estruturas de dados da thread 35
Ciclo de vida da thread 36
Material Utilizado [1] Livro Operating Systems: Principles & Practice Thomas Anderson e Michael Dahlin [2] Livro Fundamentos de Sistemas Operacionais. Editora LTC. 2015. SILBERSCHATZ, A.; GALVIN, P. B.; GAGNE, G. 37