19 - Sincronização com Mutex
Na última aula aprendemos as APIs da biblioteca pthread para criar e esperar a finalização de threads. Também aprendemos a passar argumentos e receber de volta valores usando um struct alocado dinamicamente.
Aquecimento
Vamos iniciar uma revisão trabalhando em cima do arquivo soma_global.c.
Example
Abra o arquivo e analise seu conteúdo.
Exercise 1
Resposta
Soma parte de um array (alguns elementos). Nos argumentos, o *vetor representa um ponteiro para todos os elementos do array. Já start e end representam o range de posições as quais queremos somar.
Para fins didáticos, estamos atualizando diretamente a variável soma_total dentro do for.
Example
Complete as partes faltantes e rode o programa e compile o programa soma_global.c:
gcc soma_global.c -o soma_global -pthread
Execute dois testes com entrada dos arquivos in1.txt e in2.txt.
./soma_global < in1.txt
./soma_global < in1.txt
Eles dão os resultados esperados?
Exercise 3
Resposta
Na operação soma = soma + spa->vetor[i]; a variável global soma está sendo lida, somada e posteriormente atribuída a ela própria. Entretando, entre a leitura e a atribuição, outra thread pode atualizar o valor da soma, tornando os resultados inconsistentes e imprevisíveis.
Sincronização usando mutex - Semáforo Binário
Vamos agora trabalhar agora para corrigir este erro! Lembrando da aula, as operações possíveis são as seguintes:
lock- se tiver destravado, trava e continua; se não estiver espera.unlock- se tiver a trava, a destrava e permite que outras tarefas travem.
Note que não existe garantia de ordem! Ou seja, se tiverem vários processos esperando por um mutex qualquer um deles pode receber o acesso. Inclusive, uma thread pode esperar "para sempre" e nunca receber o acesso. Não é provável, mas é possível.
Warning
Você pode precisar instalar o pacote manpages-posix-dev para obter as páginas do manual usadas neste roteiro.
Exercise 4
Resposta
Onde a soma está sendo atualizada. Damos lock antes e unlock depois da linha soma = soma + spa->vetor[i];
Example
Coloque um comentário nas linhas identificadas acima.
Já sabemos onde iremos colocar as operações de lock e unlock do mutex. Agora falta só criá-lo.
Exercise 5
Resposta
Podemos fazer isto na função main, antes da criação das threads. Para que ele seja recebido pela função, podemos criar um outro argumento na nossa struct soma_parcial_args!
Exercise 6
Exercise 7
Example
Com base nas suas respostas acima, conserte seu programa soma_global.c e verifique que ele retorna os resultados corretos.
Exercise 8
Resposta
Podemos conferir utilizando o comando time no terminal. Percebemos um aumento no tempo com o uso do mutex. Isto ocorre devido a necessidade de sincronizar o acesso à variável global soma.
Tip 1
Usar mutex é muito caro! Além de acabar com o paralelismo, as operações lock e unlock também são custosas.
Economizando mutex
Nesta parte final iremos ver como diminuir o número de chamadas ao mutex.
Exercise 9
Resposta
Podemos criar uma variável local que acumula a soma e só atualiza soma_global no fim. Seria uma variável por thread!
Example
Implemente a ideia acima e veja se houve melhora. Salve como soma-global2.c.
O exercício acima deverá ter desempenho bom, já que limitamos a quantidade de vezes que usamos o mutex. Vamos tentar outra ideia agora.
Exercise 10
Resposta
Cada thread poderia retornar na struct de argumentos o resultado de sua soma. Na função main, após cada reposta de conclusão da thread (retorno da pthread_join), poderíamos somar os resultados.
Exercise 11
Resposta
Não, pois na função main a execução da soma seria sequencial.
Example
Implemente a ideia acima e confira os resultados. Houve melhora no desempenho?