top of page

Programação multithreading e comunicação entre processos. Técnicas de sincronização e solução para o

1 Introdução


A fim de compreender o comportamento do sistema operacional, será desenvolvido um processo de comunicação cliente/servidor, utilizando programação multithread, práticas e técnicas de sincronização, além disso aplicando problema dos leitores e escritores.


2 Leitores e Escritores


O problema dos leitores e escritores refere-se a multithread acessando o mesmo espaço de memória compartilhada. Existem diversas abordagens para este tipo de problema, entretanto no desenvolvimento do sistema daremos importância aos leitores.


Enquanto um leitor estiver acessando a área de memória compartilhada, nenhum escritor poderá acessar a área de atualização. Se o leitor estiver no meio do processo para consultar a informação, os escritores deverão esperar todas as execuções de leitores, a frente dele, terminarem e ao final desbloquear a área crítica para escrita.


Serão criadas dois tipos de threads no lado do servidor, uma de leitura de dados e outra escrita, sendo esta última deverá ler o dado e atualizar a informação. Ao final do processo de leitura, a thread delibera o espaço para as outras threads que estavam em espera.


3 Desenvolvimento do Trabalho


O trabalho será desenvolvido na linguagem de programação C, em decorrência da facilidade de bibliotecas para trabalhar com sincronização e criação de sockets, além disso sua facilidade de compilar e obtenção dos resultados, iremos inspecionar o consumo de memória na tentativa de comunicação entre cliente e servidor quando utilizam diversas threads em concorrência.


Analogamente, a concorrência de processo no sistema funciona como requisições de páginas na web, onde o usuário abre diversas abas e todas rodando ao mesmo tempo, também equivale a busca de dados via banco, sendo várias pessoas solicitando a mesma informação.


Utilizaremos várias threads de leitura e escrita no lado do servidor de acordo com a ação solicitada pelo cliente, o programa será desenvolvido sobre o problema dos leitores e escritores.


O sistema será composto por cliente, sendo que este poderá enviar através de socket o valor 1, caracterizando leitura de dados ou 2 como atualização de valor. Em ambos os casos o cliente ficará no aguardo da resposta existente no servidor.


Também no servidor, conforme solicitação do cliente, irá disparar uma thread como leitura ou escrita, ao final dos dois processo irão retornar a resposta do valor atual para o cliente.


O cliente deverá conectar-se na mesma porta que o servidor estiver rodando, além disso, quando este inicializar deverá informar a quantidade de conexões e threads que ficarão em concorrência na solicitação de informações.


Partimos como premissa, que o sistema utilizará 1000 conexões do cliente com o servidor e 10 threads concorrendo em paralelo. Dentro das 10 threads serão divididas em 5 threads de leitura e 5 de escrita. Todas realizarão conexões e solicitações em paralelo. O tempo de execução em decorrência de multithread será muito rápido. Também inferimos que a concorrência de thread ocasionará a utilização de múltiplos núcleos, para trabalhar com a memória compartilhada, será demonstrado, abaixo, pontos principais do algoritmo que realizam a lógica principal na base do cliente e servidor.


3.1 Cliente


Na parte do cliente cada threads estará armazenada em uma lista e irá realizar uma solicitação de leitura ou escrita no ato de sua criação, passando por parâmetro o id. Os ids das threads estarão dentro de uma lista, alocada dinamicamente, podendo acessar elas durante o desenvolvimento do sistema.


A fim de deixar compreensível o código, dividimos o método que realiza a leitura e escrita. Será demonstrado que a leitura irá diligenciar o valor 1 para a função, “areaCritica()”, e quando for escrita enviará por parâmetro valor 2.


O método “areaCritica()” realizará a verificação do limite de conexões para os escritores e leitores, quando um destes casos atingir o limite, o sistema não permitirá o solicitação de dados, além disso, existe outra condição que é a variável run, esta caracteriza se o sistema ainda pode continuar realizando solicitações de dados em relação a quantidade total de conexões. Cada tipo de threads realizará em concorrência a chamada do método “envioRecebimento()” e “decrementaConexao()” os quais, respectivamente, se comunicará com o servidor, decrementará o total de conexões já realizada.


A estrutura de repetição contida no método fará com que um thread realize conexões e solicite a informação conforme o dado contido na variável “valor”. O envio de valor será passado por parâmetro na função “send” e recebimento do valor ocorrerá na “recv”. No método “decrementaConexao()” a cada iteração será decrementado o número de conexões antes estipulado, logo quando o valor for menor que 1, não haverá mais conexões para realizar. Utilizamos “pthread_mutex_lock” e “pthread_mutex_unlock” para controlar o acesso concorrente na região crítica.


3.2 Servidor


No lado do servidor, o sistema ficará no aguardo de alguma solicitação, e cada conexão realizada no servidor será criada um thread dependendo do tipo de solicitação recebida por parâmetro. O método “pthread_create()” cria uma nova thread, para o tipo de dado solicitado.


Quando solicitado a leitura, na criação da thread será chamada a função “readData()” a preferência no código desenvolvido se encontra no método de leitura por este motivo realizamos um mutex validando se a variável “rc” é igual a um, caso sejam ativamos o mutex no “db” fazendo com que todas as transações de escrita aguardem até todas as de leitura serem processadas. Esta liberação ocorre no fim do codigo ao validar se “rc” é igual zero liberando então o mutex do “db”. Abaixo podemos ver o trecho de código que realiza esta função.



Quando solicitado a leitura, na criação da thread será chamada a função “writeData()” esta função irá ficar aguardando até o mutex “db” descrito anteriormente for liberado, quando o mesmo for liberado irá bloquear o “db” para nenhuma leitura seja fazer alteração e então realizará a alteração de escrita na variável. Evitando assim que seja lido o dado em meio a uma atualização, gerando anti-dependência de dados (write-after-read), muito comum em arquiteturas superescalares, após será envia o valor pelo socket e decrementa o escritor.


Com a execução de multithreads realizando conexões e solicitando dados concorrentemente, observamos que ao atingir o limite de conexões conforme explicitado por parâmetro, o cliente finaliza seu processo, entretanto, observamos que estas conexões mesmo após finalizá-la mantém-se abertas.

Analisamos que ao encerrar as solicitações de cliente, no lado do servidor será demonstrado a quantidade de escrita. Esta ratificação serve como prova da quantidade de escritas, conforme solicitado no início do trabalho.


Observamos que na aplicação de política NUMA, quando há mais de um processo tentando armazenar/atualizar a mesma posição de memória na cache, esta política pode executar de forma incorreta vários processos, que acessarão a área de memória de forma rápida e sucessivamente. A fim de evitar um bloqueio de programação, o sistema operacional tenta reduzir a frequência de acesso, através de atribuição de processadores.

Conforme nossa premissa, o sistema realizou o comportamento esperado, em relação a multithread, executando 1000 conexões com 10 threads em menos de 30 segundos.


4.0 Conclusão


Conseguimos demonstrar o comportamento de multithtread em ambiente cliente e servidor, sejam eles rodando com poucas threads 10 ou muitas threads 1000, a fim de gerar um estresse computacional.


Com nossa computação lógica de dando preferência aos escritores, quando solicitado, e as diversas iteração concorrentes em áreas críticas, conseguimos extrair as informações acima explicitadas, o que futuramente, podemos reutilizar esta lógica para os diversos tipos de problemas que ensejam programação concorrente.


Esta análise serviu para demonstrar além do comportamento de multithread, a forma básica de como funciona requisições entre cliente e servidor, no comportamento da busca de informações no banco de dados.


Segue o link para baixar o código: https://github.com/RenanCS/Prog_Multithread_Cliente_e_Servidor_Leitores_e_Escritores.git


bottom of page