Laboratório 11 - 30/5/2001

Na aula passada discutimos um padrão de observador, muito comum em programas orientados a objetos, onde um observador registra seu interesse em determinado evento através de uma função de callback a ser chamada quando esse evento ocorrer.

Em C podemos simular esse comportamento, apesar de não termos o conceito de objetos, registrando endereços de funções que devem ser chamadas quando o evento ocorrer. Como essas funções não correspondem a objetos específicos, é comum que precisemos registrar alguma outra informação que caracterize o interessado no evento.

Como exemplo, considere a situação onde o sujeito é um thread responsável por gerenciar um valor inteiro e os observadores são threads interessadas em alterações desse valor. O programa exemplo mostra um programa bem simples que modela essa situação. Nesse programa, todos os observadores seguidamente se bloqueiam a espera de alterações no valor observado, e a função de callback recebe como parâmetro a condição específica de cada observador (essa é a informação que caracteriza cada observador).

  1. Acompanhe a execução desse programa e entenda como ele funciona. Repare que com a estrutura apresentada, um observador poderia "perder" valores, isto é, poderia não conseguir imprimir todos os valores de valorobservado.
  2. Modifique seu programa do broadcast atômico para que ele passe a utilizar uma estrutura de callback. Agora, quando um consumidor não encontrar um item novo, ele não deve mais se bloquear dentro do monitor, e sim registrar uma função de callback e uma informação adicional para ser avisado quando aparecer um novo item. (Associe a cada posição do buffer um array onde cada posição contém um endereço de função e um ponteiro void *) Ao voltar da retira, o consumidor deve verificar se teve sucesso, e, em caso negativo, deve se bloquear. Pense nas condições de corrida e sequências de bloqueios que podem acontecer agora...
  3. Modifique o programa para incluir dois tipos de threads consumidores. O primeiro tipo de consumidor é o anterior. Um novo tipo de consumidor deve registrar uma função de callback que altera uma variável do consumidor. Esse consumidor novo não se bloqueia enquanto espera as alterações: ele fica em um loop, simulando que está fazendo alguma atividade (por exemplo, periodicamente escrevendo um caracter na tela), e de tempos em tempos verifica se a variável foi alterada ou não. Nesse caso você vai trabalhar com diferentes funções de callback. A informação específica de cada thread consumidor não vai mais ser uma condição no segundo caso.