570 likes | 681 Vues
Programação Avançada Processos, Threads e IPC. Prof. Natalia Castro Fernandes Mestrado em Telecomunicações – UFF 2º semestre/2012. Introdução. Processos Threads IPC – Interprocess Communication Memória Compartilhada Sockets Pipes. Processos. Processo Programa em execução
E N D
Programação AvançadaProcessos, Threads e IPC Prof. Natalia Castro Fernandes Mestrado em Telecomunicações – UFF 2º semestre/2012
Introdução • Processos • Threads • IPC – Interprocess Communication • Memória Compartilhada • Sockets • Pipes
Processos • Processo • Programa em execução • Execução sequencial • Inclui: • Contador de programa • Pilha • Seção de dados
O processo • Atividade atual, incluindo o contador de programa e os registradores de processo • Contém múltiplas partes • Pilha com dados temporários • Parâmetros de funções, endereço de retorno, variáveis locais ? • Heap • Memória alocada dinamicamente • Memória • Seção de dados • Contém variáveis globais • Código, também chamado de seção de texto
Processos • Programa x processo • Programa é passivo • Processo é ativo • O programa se torna um processo quando é carregado na memória Usuário Execute A Sistema Operacional Busca programa no disco Processo é criado! Disco Carrega programa na memória Memória A A
Criando processos com o fork #include <sys/types.h> #include <studio.h> #include <unistd.h> int main() { pid_t pid; /* fork anotherprocess */ pid = fork(); if (pid < 0) { /* erroroccurred */ fprintf(stderr, "Fork Failed"); return 1; } elseif (pid == 0) { /* childprocess */ execlp("/bin/ls", "ls", NULL); } else { /* parentprocess */ /* parentwill wait for thechild */ wait (NULL); printf ("Child Complete"); } return 0; } Exemplos em Python: teste_fork3.py até teste_fork7.py Atenção!!!!! O fork só funciona em Linux para Python
Finalização de processos • Processo executa a sua última linha e pede ao sistema operacional para deletá-lo (exit) • Recursos do processos são desalocados pelo sistema operacional • Processo pai pode terminar a execução de um processo filho (abort) • Exemplos de motivação • Processo filho excedeu os recursos alocados • Tarefa passada ao filho não é mais necessária • Processo pai está sendo finalizado, e alguns sistemas operacionais não permitem que o filho continue executando após o pai ter sido finalizado • Finalização em cascata
Threads • Threads são processos leves • Diferentes tarefas da aplicação podem ser implementadas como threads separados • Aplicável apenas às tarefas que possam ser paralelizadas • Exemplos: • Atualizar o display • Buscar dados • Verificar correção ortográfica
Motivação • A criação de processos é muito custosa • Criação de threads é mais simples • Vantagens do uso de threads • Simplificação do código • Aumento da eficiência
Benefícios • Melhora o tempo de resposta • Programa continua executando mesmo que algum thread seja bloqueado (thread no espaço de kernel) • Compartilhamento por padrão dos recursos do processo • Não precisa de técnicas para criar uma memória compartilhada • É mais simples criar um thread do que um processo • Não precisa alocar novos recursos e é fácil trocar contexto entre threads • Aumenta o paralelismo • Em máquinas com multiprocessamento
Programação com múltiplos cores • Sistemas com múltiplos cores impulsionam novas formas de programar • Maior eficiência na execução de programas • Contudo, existem algumas dificuldades É possível paralelizar? As tarefas paralelizáveis tem a mesma importância? Quais conjuntos de dados pertencem a cada thread? Se duas threads usam dados dependentes, como sincronizar o acesso? Como depurar o processo, se existem múltiplos caminhos de execução?
Threads • Exemplos • Ex_sem_thread.py • Ex_sem_thread_processo.py • Ex_thread.py • Ex_threadv2.py • Ex_thread_mais_legal.py • Ex_thread_mais_legal_completo.py • Ex_legal_final.py
Comunicação interprocessos • Processos em um sistema podem ser independentes ou cooperativos • Processos cooperativos podem afetar ou serem afetados por outros processos • Processos independentes não podem afetar ou serem afetados pela execução de outro processo • Razões para cooperação interprocessos • Compartilhamento de dados • Aumento da velocidade de computação • Modularidade • Conveniência
Comunicação interprocessos • Cooperação entre processos depende da interprocess communication(IPC) • Dois modelos de IPC • Memória compartilhada • Troca de mensagens
Modelos de comunicação Memória compartilhada Envio de mensagens
Sincronização • Envio de mensagens pode ser tanto blocante quanto não-blocante • Operação blocante é considerada síncrona • O send blocante bloqueia o emissor até que a mensagem seja recebida • O receive blocante bloqueia o receptor até que a mensagem esteja disponível • Operação não-blocante é considerada assíncrona • O emissor envia a mensagem e continua • O receptor recebe uma mensagem válida ou nulo
Exemplos em Python • Compartilhamento de variáveis • shared_mem.py a shared_memv3.py • OBS: • Multiprocessing – API para processos cooperativos • Queue() – Cria fila compartilhada • put(), get() • Process(codigo do novo processo, argumentos) • start(), is_alive()
Exemplos em Python • Compartilhamento de variáveis • shared_memv4.py • Multiprocessing • Value(tipo, valor) --- compartilhando um número • Array(tipo,lista) • Join() – Bloqueia o pai até o filho terminar • Shared_memv4b.py
Comunicaçãoem sistemas cliente-servidor • Sockets • Pipes
Sockets • Um socket é definido como um ponto de extremidade para a comunicação • Concatenação de IP e porta • Exemplo: • O socket 161.25.19.8:1625 se refere a porta 1625 no host 161.25.19.8 • A comunicação consiste de um par de sockets
Exemplos em Python • Socket • Socket-client.py e socket-server.py • Socket-client-udp.py e socket-server-udp.py • Socket-client.py e socket-server-multiple.py
Pipe • Atua como um condutor permitindo que dois processos se comuniquem • Pipes comuns permitem a comunicação em um estilo produtor-consumidor padrão • Funcionamento • O produtor escreve em uma ponta do pipe • O consumidor lê a saída na outra ponta • Portanto, pipes comuns são unidirecionais • Requerem relação de pai-filho entre processos comunicantes
Pipes nomeados • Pipes nomeados são mais potentes que pipes comuns • Comunicação bidirecional • Não há necessidade de relação pai-filho • Pode ser usado por diversos processos • Disponível tanto em UNIX quanto em Windows
Exemplos em Python • Pipe • Ex-pipe.py
Acesso Concorrente • Acesso concorrente a um dado compartilhado pode resultar em inconsistência • Para manter a consistência, é preciso garantir uma execução ordenada dos processos cooperativos (que estão interagindo por meio de uma estrutura compartilhada) • Estudo da concorrência entre processos através de modelos • Exemplo: Produtor-consumidor
Problema do Produtor-Consumidor • Produtor e consumidor são dois processos distintos • O produtor produz e coloca o produto no buffer • O consumidor consome produtos que estão no buffer • Consequências • O buffer passa a ser uma estrutura compartilhada • O produtor não pode colocar um produto em um buffer cheio • O consumidor não pode consumir um produto se o buffer estiver vazio • A variável compartilhada count pode ser usada para saber o número de elementos no momento dentro do buffer • Eventualmente, o count pode ser atualizado simultaneamente pelo produtor e pelo consumidor • Atualização não é uma tarefa atômica (indivisível)
Produtor def produtor(): seed() global count i=0 while (True): sleep(random())#Tempo parasimular o processoproduzindo a informação if count<BUFFER_SIZE: buffer.append(str(i)) count = count +1 i=i+1 else: while (count>=BUFFER_SIZE): pass
Consumidor def consumidor(): seed() global count while (True): if count>0: buffer.pop(0) count = count -1 sleep(random()) #Tempo parasimular o processoconsumindo a informação else: while(count<=0): pass
Condição de disputa • O incremento do contador pode ser implementado da seguinte forma:register1 = counter register1 = register1 + 1 counter = register1 • O decremento do contador, por sua vez, seria:register2 = counter register2 = register2 - 1 count = register2
Condição de disputa • Suponha que, no momento, count=5 e que o produtor e o consumidor estão sendo executados: t0: produtor: register1 = counter {register1 = 5}t1: produtor: register1 = register1 + 1 {register1 = 6} t2: consumidor: register2 = counter {register2 = 5} t3: consumidor: register2 = register2 - 1 {register2 = 4} t4: produtor: counter = register1 {count = 6 } t5: consumidor: counter = register2 {count = 4}
Condição de disputa • Definição • Situação em que várias tarefas acessam dados concorrentemente e o resultado da execução depende da ordem específica em que o acesso ocorre
Condição de disputa • Solução para a condição de disputa: • Sincronização de processos • Proteção das seções críticas • Uma seção crítica (ou região crítica) é um trecho de código no qual a tarefa está alterando uma estrutura compartilhada (variáveis compartilhadas, tabelas, arquivos, etc.) • Dessa forma, se um processo entra na sua seção crítica, nenhum outro processo que compartilhe alguma estrutura pode ser autorizado a entrar na sua seção crítica
Problema da seção crítica • Problema de como controlar o acesso às seções críticas de processos que compartilham dados • Ideia: Criar um protocolo de acesso • Processo pede autorização para entrar na seção crítica • Quando autorizado, processo executa seção crítica • Processo avisa que terminou sua seção crítica • Processo continua a sua execução
Problema da seção crítica A entra na região crítica A deixa a região crítica Processo A B entra na região crítica B deixa a região crítica B tenta entrar na região crítica Processo B B bloqueado T1 T3 T4 T2
Soluções para o problema da região crítica • Exclusão mútua • Nunca dois processos podem estar simultaneamente em suas regiões críticas • Progresso • Nenhum processo fora da sua região crítica pode bloquear outros processos • Espera limitada • Nenhum processo deve esperar eternamente para entrar em sua região crítica Nada pode ser afirmado sobre a velocidade de processamento ou sobre o número de CPUs disponíveis
Revendo o Produtor-consumidor • Qual é/são as seções críticas do código? • Uso da variável count • Uso da variável buffer • Consumidor def consumidor(): seed() global count while (True): ifcount>0: buffer.pop(0) count = count -1 sleep(random()) #Tempo para simular o processo consumindo a informação else: while(count<=0): pass Seção crítica
Revendo o Produtor-consumidor • Produtor def produtor(): seed() global count i=0 while (True): sleep(random())#Tempo para simular o processo produzindo a informação ifcount<BUFFER_SIZE: buffer.append(str(i)) count = count +1 i=i+1 else: while (count>=BUFFER_SIZE): pass Seção crítica
Revendo o Produtor-consumidor if __name__ == '__main__': ###Programa principal print "Esse programa simula o problema do produtor-consumidor, utilizando uma lista compartilhada por n threads: os produtores e os consumidores" print "Está se utilizando o método da espera ocupada." count = 0 prod=20 #Numero de produtores cons= 20 #Numero de consumidores t=[1]*(prod+cons) #Inicio o número de threads for i in range(prod): t[i] = Thread(target=produtor, args=()) t[i].start() for i in range(prod,prod+cons): t[i] = Thread(target=consumidor, args=()) t[i].start() while (True): if (len(buffer) != count): print "ERRO!!!!, pois tamanho do buffer = %d e count = %d" % (len(buffer),count) sleep (0.1) if (len(buffer)>10): print "ERRO!!!!, pois o buffer estourou!!!" Seção crítica Seção crítica
Revisão • Como proteger a seção crítica? • Enquanto um produtor está na seção crítica, nenhum outro produtor ou consumidor pode entrar na seção crítica • Seção crítica = seção aonde se atualiza ou se compara as variáveis compartilhadas • Enquanto um consumidor está na seção crítica, nenhum outro consumidor ou produtor pode entrar na seção crítica
Semáforos • wait (S) { • while S <= 0 • ; // no-op • S--; • } • Signal e wait • Operações atômicas na modificação do semáforo • Modificam o valor do sinal Se S=1, decrementa S e pode entrar. Caso contrário, fica esperando em espera ocupada intsinal=1; wait(sinal); Regiãocrítica; signal (sinal); Espera ocupada = espera usando CPU • signal (S) { • S++; • }
Deadlock • Deadlock – Acontece quando dois ou mais processos estão esperando por um evento que só poderia ser executado por um dos processos em espera • Exemplo • Imagine que S e Q são dois semáforos inicializados em 1 P0P1 wait (S); wait (Q); wait (Q); wait (S); . . . . . . signal (S); signal (Q); signal (Q); signal (S);
Resolvendo o Produtor-Consumidor • Buffer de tamanho N • Semáforo mutex inicializado em 1 • Semáforo full inicializado em 0 • Semáforo empty inicializado em N Controla o acesso ao buffer Controla o número de espaços ocupados no buffer Controla o número de espaços vazios no buffer
Resolvendo o Produtor-Consumidor • Produtor do { // produce an item in nextp wait (empty); wait (mutex); // add the item to the buffer signal (mutex); signal (full); } while (TRUE); Espera ter um vazio para usar e usa Pede acesso ao buffer Não pode trocar a ordem! Libera o acesso ao buffer Aumenta o número de espaços ocupados
Resolvendo o Produtor-Consumidor Espera ter um cheio para usar e usa • The structure of the consumer process do { wait (full); wait (mutex); // remove an item from buffer to nextc signal (mutex); signal (empty); // consume the item in nextc } while (TRUE); Pede acesso ao buffer Libera o acesso ao buffer Aumenta o número de espaços vazios
Semáforos e Locks em Python • threading.Lock() • Retorna um objeto do tipo lock • Usa acquire() e release() • Uma vez que um thread dê um acquire, os demais threads que tentarem fazer o acquire ficarão bloqueados até que o thread dê um release • Apenas um é liberado e os demais, caso existam, ficam na fila • threading.RLock() • Semelhante ao Lock, mas um mesmo thread pode pedir o Rlock mais de uma vez sem se auto-bloquear. Contudo, o número de releases precisa ser igual ao número de acquires para liberar o Lock
Exemplos em Python • Ex_threadv2.py • Ex-thread-lock.py • Ex-thread-lockv2.py • Ex-thread-lockv3.py • Ex-thread-lockv4.py
Semáforos e Locks em Python • threading.Semaphore([value]) • Retorna um objeto do tipo semáforo • Recebe um valor inicial do contador e bloqueará apenas quando o número ficar negativo • Valor padrão do contador é 1 • threading.BoundedSemaphore([value]) • Semelhante ao Semaphore, mas garante que o valor inicial nunca será excedido. • Valor padrão do contador é 1
Exemplos em Python • Ex-semaphore.py • Ex-semaphorev2.py • Ex-semaphorev3.py • Ex-semaphorev4.py • Ex-semaphorev5.py