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 );
}
Download

Threads