conceitos de programação paralela - 2 mo801/mc972
TRANSCRIPT
Conceitos de Programação Paralela - 2
MO801/MC972
Visão Geral
• Existem várias bibliotecas disponíveis para programação– Mesmos conceitos básicos– Implementações diferentes
• Exemplo:– Windows Threads– POSIX Threads (pthread))
Sincronização
• É um mecanismo para impor restrições na ordem de execução das threads– Evita que um
comportamento não desejado aconteça
• Pouco usada diretamente pelos programadores
Entrada
Saída
Shared data or critical section
Região Crítica
• É uma região do código onde existem variáveis das quais dependem múltiplas threads
• Somente uma thread pode estar numa região crítica por vez
• Pode ocasionar contenção se for uma grande região de código com grande concorrência
Dois casos comuns
• Mutual exclusion– Uma thread bloqueia uma região e todas as
outras ficam aguardando sua vez de entrar na região
• Condition synchronization– A thread é bloqueada aguardando por uma
determinada condição
Exemplo Genérico
<Entrada na região crítica, faz com que outras threads fiquem aguardando>…Região crítica…
<Saída da região crítica, permite que outras threads entrem>
Deadlock
• Ocorre sempre que uma thread está esperando um lock de outra thread que nunca ficará disponível
• Pode ser de 3 tipos diferentes– Self-deadlock– Recursive deadlock– Lock-ordering deadlock
Self deadlock
• A própria thread possui um lock que ela deseja, mas executa uma operação de bloqueio aguardando o lock
• Não deveria ocasionar problema– O programador pode testar se já possui o
lock antes– O sistema operacional pode saber se a
thread já possui o lock
Recursive deadlock
• A thread i possui um lock e, antes de liberá-lo, precisa de algo da thread j, que por sua vez tenta obter o mesmo lock
• Esse problema pode escalar para dentro de bibliotecas
Lock-ordering deadlock
• Ocorre quando, por exemplo, a thread A possui o lock i e quer o lock j e a thread B possui o lock j e quer o lock i
• É o tipo mais comum de deadlock
• Pode ser corrigido sempre solicitando os locks na mesma ordem
Primitivas de sincronização
• São implementadas sobre operações atômicas fornecidas pelo sistema de memória (memory fence, ou memory barrier)– Test and set– Read and increment– Compare and swap
• Três tipos de primitivas– Semaphores– Locks– Condition variables
Semaphores
• Permite o acesso a uma ou mais threads numa região de código
• Utiliza duas operações: P(s) e V(s)
• O valor armazenado indica– Quantas threads ainda podem entrar na
região, se positivo– Quantas threads estão aguardando para
entrar na região, se negativo
Pseudo-código
P(s) {atomic { sem=sem-1; temp=sem }
if (temp < 0){ Thread bloqueada }
}V(s) {atomic { sem=sem+1; temp=sem }
if (temp <= 0){ Libera uma thread bloqueada
}}
Tipos de semáforos
• Do ponto de vista da usabilidade, existem dois tipos de semáforos– Forte (strong)
• A ordem de solicitação é garantida como ordem de obtenção
– Fraco (weak)• A ordem de solicitação não é garantida como
ordem de obtenção
Exemplo
semaphore ss.sem = 1beginT: <região não crítica>
P(s) <região crítica> V(s) Goto T
end
Locks
• Similares aos semáforos, só que com apenas uma thread na região crítica
• Duas operações– aquire()
• Aguarda atomicamente pela disponibilidade de lock e ativa o lock
– release()• Atomicamente desativa o lock
• A granularidade influencia fortemente no nível de contenção do sistema
Exemplo
{define all necessary locks}<Start multithreading blocks>…<critical sectionn start><aquire lock L>… operate on shared memory protected by lock L …
<release lock L><critical section end>…<End multithreading blocks>
Tipos de locks
• Vários tipos de locks podem estar disponíveis nas APIs de programação
• Evite utilizar mais de um tipo de lock no mesmo programa– Muito cuidado com bibliotecas
• Mutexes, Recursive Locks, Read-Write Locks, Spin Locks
Mutexes
• É o lock mais simples– Uma chamada a aquire ativa e uma chamada
a release desativa o lock
• Algumas implementações podem utilizar timers para liberar o lock– Utilize try-finally para evitar deadlock
Recursive locks
• Podem ser obtidos várias vezes pela mesma thread e precisam ser liberados o mesmo número de vezes– Nenhuma outra thread pode obter o lock
simultaneamente
• Muito utilizado em código recursivo• Locks recursivos são mais lentos de
implementar que os não recursivos em geral
Read-Write locks
• Também chamados de shared-exclusive ou multiple-read/single-write locks
• Permitem que vários locks de leitura sejam obtidos mas limitam o acesso à escrita a apenas uma thread (sozinha)
• Útil quando muitas threads tentam acessar os mesmos dados mas existem poucas escritas
Spin locks
• Não bloqueiam a thread, que deve ficar tentando o lock sucessivamente
• Permite evitar a troca de contexto quando ela é mais cara que a tarefa da região crítica
• Cuidado com starvation
Condition variables
• Similares aos semáforos, mas sem uma variável armazenada
• Três operações básicas– wait
• Atomicamente libera o lock e aguarda, retorna quando o lock tiver sido obtido novamente
– signal• Permite que uma das threads em wait executem, ao
retornar, o lock estará ativo novamente
– broadcast• Permite que todas as threads aguardando pelo lock
executem, ao retornar, o lock estará ativo novamente
Troca de mensagens
• É um método especial de comunicação que faz transferência de informação ou sinal de um domínio para outro
• Fortemente dependente do domínio
• Três M– Multi-granularity– Multithreading– Multitasking
• Em geral, associadas a processos ao invés de threads– Intra-process
• Entre duas threads do mesmo processo
– Inter-process• Entre threads de processos diferentes
– Process-Process• Entre dois processos diferentes
Mecanismos de controle de fluxo
• São utilizados como sincronização em sistemas multiprocessados
• Fence– Geralmente associado à memória– Todas as operações de memória anteriores são
completadas e as posteriores aguardam que a operação atual termine
• Barrier– Associado a threads colaborativas– Um ponto do código é definido como uma barreira
quando todas as threads do grupo só passam desse ponto juntas
Questões dependentes de implementação
• Bibliotecas de threads implementam APIs em cima dos recursos fornecidos pelo sistema operacional– Algumas funcionalidades podem variar– Podem existir chamadas aparentemente
idênticas que devem ser executadas em situações diferentes