Threads
Bauke A. Dijkstra
Lucas Alves
Allan F. Furukita
Threads
• Threads, como processos são mecanismos para
permitir um programa fazer mais de uma coisa ao
mesmo tempo
• Como processo as threads aparentam correr
simultaneamente
• O Linux kernel interrompe cada thread de tempo
em tempo para dar chance de outros executar
• Conceitualmente uma threads está dentro do
processo
• Quando é executado um programa, é criado um
processo e dentro desse é criado uma única thread que
pode criar outras threads
• Ao criar uma nova thread nada é copiado, pois eles já
compartilham a mesma memória, espaço, descrição, e
outros recursos do sistema
• Se uma thread mudar o valor de uma variável a thread
subsequente ira ver o valor alterado
• GNU/Linux implementa o POSIX thread API, todas
funções e tipo de data são declarados no cabeçalho do
arquivo
• Como a pthread não esta incluido na biblioteca padrao
de C, para compilar você deve adicionar –lpthread pois
ela esta na libpthread
Criação de threads
• Cada thread possui sua ID
• Na criação em a thread executa a thread function,
quando retorna algum valor é porque ela existe
• Em C e C++ usam o tipo pthread_t
• A função pthread_create possui 4 parametros,
(id, atributo, ponteiro da função, argumento)
• A chamada de pthread_create retorna imediatamente
e a thread original continua a executar as instruções
seguindo a chamada, enquanto a nova thread começa
a função thread
• Seu programa não pode depender da ordem de
execuções das threads
Como passar dados para Threads?
• É utilizado o argumento para a passagem de
dados
• Normalmente é definido uma estrutura para
cada função thread que possui os parametros
que é esperado
Juntando Threads
• É feita a partir da função pthread_join, que
possui dois parâmetros. O id da thread que
tem que esperar e um ponteiro que irá
receber o retorno da thread quando terminar
• Caso não seja necessário o valor de retorno
utiliza o NULL
• Caso seja diferente de NULL o valor de retorno
será armazenado no local apontado pelo
argumento
Mais sobre Thread’s Ids
• A função pthread_self retorna o a ID da thread
que foi chamada
• A função pthread_equal serve para comparar
os ID das threads
Atributos de Threads
• Providencia um mecanismo para melhorar o
comportamento da thread
• Você pode criar e customizar atributos de objeto
da thread para especificar outros valores para os
atributos
• Para especificar uma thread customizada você
deve, criar pthread_attr_t, chamar
pthread_attr_init, modificar o atributos de objeto
da thread para conter o que é desejado, passar
um ponteiro para o objeto quando chamar
pthread_create, chamar pthread_attr_destroy
• Um unico atributos de objeto da thread pode ser usado
para começar varias threads
• Não é necessário deixar o atributos de objetos da
thread ao redor depois que as threads foi criada
• Uma Thread pode ser declarada como joinable thread
ou como detached thread
• Joinable thread não é limpada automaticamente
quando terminada já a detached thread limpa
automaticamente e por isso essa não pode ser
sincronizada com outra thread
• Para escolher o estade detach no atributo de objeto da
thread utiliza pthread_attr_setdetachstate sendo o
primeiro parametro um ponteiro para o atributo de
objeto da thread, e o segundo o estado desejado de
detach
Cancelamento da Thread
• A função utilizada é a pthread_cancel, passando a
ID da thread
• Ela pode ser juntada depois de cancelada
• Para liberar seus recursos ela deve ser juntada
• Temos três estados em relação ao cancelamento
das threads
• Cancelamento assíncrono
• Cancelamento síncrono (padrão)
• Incancelável
Threads Síncrona e Assíncrona
• Uma thread cancelável assíncrona pode ser
cancelada em qualquer ponto da execução, já a
síncrona pode ser cancelada em pontos
específicos da execução
• Para deixar uma thread cancelável assíncrona
utilize pthread_setcanceltype, que afeta a thread
que chamou a função.
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
• Para criar pontos de cancelamento utiliza-se o
pthread_testcancel
Seções Críticas Incancelável
• A thread pode desabilitar o cancelamento
usando a função pthread_setcancelstate
• pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);
• Usando a função acima habilita a
implementação de seções críticas
• Seções críticas é uma seqüência de códigos
que não pode ser cancelado enquanto não
estiver pronto
Quando usar cancelamento da Thread
• No geral não é aconselhável utilizar
cancelamento de uma thread para finalizar a
execução de uma thread, exceto em
circunstancias incomuns.
• A melhor estratégia é indicar a thread que
deve sair e esperar ela terminar de maneira
normal.
Dados específicos da Thread
• As threads pertencentes a um processo
compartilham os dados do processo.
• Esse compartilhamento de dados fornece um
dos benefícios da programação multithreads.
• Em certas circunstancias, cada thread poderia
precisar de sua própria copa de certos dados.
• Você pode criar quantos dados específicos da
thread que quiser, sendo do tipo void*
• Cada item é referenciado por uma chave
• Para criar uma nova chave é usado a função
pthread_key_create
• O primeiro argumento é um ponteiro para a
variavel pthread_key_t
• O segundo argumento para pthread_key_t é
uma função de limpeza, pode-se passar null
caso não precise de uma função de limpeza.
• Depois da chave ser criada, cada thread pode
definir seu valor de thread-específico
correspondendo a chave chamando
pthread_setspecific.
• O primeiro argumento é a chave
• O segundo argumento é o valor do void*
thread-específico para armazenar
• Para recuperar os dados do thread-específico,
se chama pthread_getspecific, passando a
chave como seu argumento.
Cleanup Handlers
• São funções chamadas quando a thread é
terminada ou cancelada.
• Ela é útil por ser capaz de especificar funções de
limpeza sem criar um novo dado de thread
específico que esta duplicado pra cada thread.
• O GNU/LINUX te da esse recurso com esse
proposito.
• O handler toma um parâmetro void* e o valor do
argumento é dado quando o handler é
registrado.
• Um Cleanup Handler é uma medida
temporária, usada para desalocar um recurso
somente se a thread é fechada ou cancelada.
• Caso a thread não sair ou for cancelada, o
cleanup handler deve ser removido e o
recurso desalocado explicitamente.
• Para registrar um cleanup handler, deve-se
chamar pthread_cleanup_push, passando um
ponteiro para a função de limpeza e o valor do
argumento void*
• A chamada para pthread_cleanup_push deve
ser equilibrada por uma chamada
correspondente para pthread_cleanup_pop,
que cancela o registro de limpeza handler.
• Como uma conveniência
pthread_cleanup_pop leva um argumento int
flag
• Se a flag é diferente de zero, a ação de limpeza
é realizada, uma vez que não é registrada.
Thread Cleanup em C++
• Quando os objetos saem do escopo, C++ garante que
os destructors sejam chamados para essas variáveis
automáticas, fornecendo um código de limpeza que é
chamado não importando como o bloco foi encerrado.
• Se uma thread chama pthread_exit, a linguagem C++
não garante que os destructors sejam chamados para
todas variáveis automáticas
• A melhor forma para recuperar essa funcionalidade é
usar pthread_exit no nível superior da função thread
jogando uma exceção especial
Sincronização e Sessões
Críticas
• Programar com threads é muito complicado,
por causa da sua concorrência. É impossível
saber quando o sistema irá agendar e executar
uma thread.
• Para fazer um “debbuging” é complicado, pois
você pode executar o programa e funcionar,
mas na próxima ele pode falhar.
Condições de Corrida
• A causa definitiva da maioria dos problemas que
envolvem threads é que estes estão acessando os
mesmos dados. Se uma thread usa parcialmente
uma estrutura de dados enquanto outra acessa a
mesma estrutura, problemas são suscetíveis de
acontecer.
• Quando uma thread fica agendada mais ou
menos frequentemente que outra pode ocorrer
um erro conhecido como condição de corrida.
A depuração de programas que contenham
condições de corrida não é nada simples. Os
resultados da maioria dos testes não
apresentam problemas, mas em algum
momento algo estranho pode acontecer.
Sessões Críticas
O problema de evitar condições de disputa pode
ser formulado de um modo abstrato. Durante
uma parte do tempo, um processo está
ocupando fazendo coisas que não levam a
condições de disputa. Porém, algumas vezes
ele precisa ter acesso à memória ou outra
coisas que pode ocasionar disputas. Na parte
do programa onde há acesso à memória
compartilhada é chama ou sessão critica ou
região crítica
Porém, mesmo que evite-se as condições de
disputa, é necessário satisfazer 4 condições
para chegar a uma boa solução:
1. 2 Processos nunca podem estar
simultaneamente em suas regiões críticas.
2. Nada pode ser afirmado sobre a velocidade
ou sobre o número de CPUs.
3. Nenhum processo executando fora de sua
região crítica pode bloquear outros processos
4. Nenhum processo deve esperar eternamente
para entrar em sua região crítica.
Exclusão mútua
• É um modo de assegurar que outros processos
sejam impedidos de usar uma variável ou um
arquivo compartilhado que já estiver em uso
por um processo.
Semáforos
• É uma variável especial que tem como função
o controle de acesso a recursos
compartilhados. O valor de um semáforo
indica quantas threads podem ter acesso a um
recurso compartilhado.
• Edsger Dijkstra, criador do semáforo propôs a
existência de duas operações, down e up (ou
sleep e wakeup, respectivamente).
• Down: Verifica se o valor do semáforo é maior
que zero. Se sim, decrementará o valor e
prosseguirá. Se o valor for zero, o processo
será posto para dormir, sem terminar o down,
pelo menos por enquanto.
• Up: Incrementa o valor de um dado semáforo.
Se um ou mais processos estivessem
dormindo naquele semáforo, um deles seria
escolhido pelo sistema e seria dada a
permissão para terminar seu down.
Mutexes
Um mutex (abreviação de mutual exclusion,
exclusão mútua) é uma versão simplificada de
semáforo e só é adequado para gerenciar a
exclusão mútua de algum recurso ou parte de
código compartilhada.
• Mutexes são fáceis de implementar e
eficientes, o que os torna especialmente úteis
em pacotes de threads implementados
totalmente no espaço do usuário.
• Um mutex é uma variável que tem dois
estados (desimpedido ou impedido) e
consequentemente, somente 1 bit é
necessário para representá-lo.
Quando um thread precisa ter acesso a uma
região crítica, ele chama mutex_lock. Se o
mutex estive desimpedido, a chamada
prosseguirá.
Por outro lado, se o mutex já estiver impedido, o
thread que chamou mutex_lock, permanecerá
bloqueado até que o thread na região crítica
termine e chame mutex_unlock.
Se vários threads estiverem bloqueados sobre o
mutex, um deles será escolhido e liberado
para adquirir a trava.
Barreiras
Algumas aplicações são divididas em fases e têm
como regra que nenhum processo pode
avançar para a próxima fase até que todos os
processos estejam prontos a fazê-lo. Isso pode
ser conseguido por meio da colocação de
uma barreira no final de cada fase. Quando
alcança a barreira, um processo permanece
bloqueado até que todos os processos
alcancem a barreira.
Implementação de thread GNU/Linux
• A implementação de threads POSIX no
GNU/Linux é diferente da implementação em
outros sistemas UNIX.
• No GNU/Linux threads são implementados
como processos.
• Quando se chama pthread_create para criar
uma nova thread, o Linux cria um novo
processo para executar a thread
Manipulação de sinais
• O comportamento de interação entre sinais e
threads variam de um sistema tipo UNIX para
outro.
• No GNU/Linux o comportamento é ditado por um
fato que as threads são implementadas como
processos.
• Uma vez que cada thread é um processo
separado, e porque um sinal é emitido para
determinado processo, não há ambiguidade
sobre qual thread recebe o sinal.
• Normalmente sinais são enviados de fora do
programa ao processo correspondente ao
thread principal.
• Em um programa multithread, é possível para
um thread enviar um sinal específico para
outro thread, usando a função pthread_kill.
Passando o ID da thread como seu primeiro
parâmetro e um numero de sinais como seu
segundo parâmetro.
Chamada de Sistema clonada
• A chamada de sistema clonada do Linux é uma
forma generalizada de fork e pthread_create que
permite especificar os recursos compartilhados
entre o processo de chamada e o novo processo
criado.
• O clone requer que especifique a região da
memória para a execução da pilha que o novo
processo use.
• Para criar novos processos use fork
• Para criar novas threads use pthread_create
Processos x Threads
• Todos threads em um programa deve rodar o
mesmo executável. Um processo filho pode rodar
um executável diferente chamando uma função
exec.
• Um thread errante pode prejudicar outros
threads no mesmo processo porque
compartilham o mesmo espaço de memória
virtual e outros recursos. Um processo errante
não prejudica pois cada processo tem uma cópia
do espaço de memória do programa.
• Threads deve ser usada em sistema paralelo
de grão fino, em que o problema pode ser
dividido em várias tarefas semelhantes.
Processo deve ser usado em sistema paralelo
de grão grosso.
• Compartilhamento de dados entre threads é
trivial porque threads compartilham a mesma
memória. O compartilhamento de dados
entre os processos requer o uso de
mecanismos de IPC.
Download

Threads