quinta-feira, 2 de junho de 2011

[OFF] semaphore.h e pthread.h no Dev C [Win32]

Primeiramente, se você veio aqui para dizer "-Pra quê tudo isto.... Pega qualquer distribuição  Linux e  compila o código...." e não entende que as pessoas podem querer utilizar o Ruimdows para fazer este tipo de coisa, eu vou te pedir um favor: Clique aqui. Para o restante, siga.
Segundamente, são dois motivos que me levaram a publicar este conteúdo que me rendeu horas de pesquisa pela web:

  1. Centralizar este tipo de informação, para que as pessoas que encontram problemas na implementação de código C em ambiente Windows.
  2. Ajudar meus nobres e ilustres colegas de graduação.
Explicações à parte, vamos ao que interessa.

Faça o download do pacote Pthreads (Posix Threads) aqui. Este pacote permite a você usar várias bibliotecas nativas do C padrão.Entre elas semaphore.h ,pthread.h e outras relativas à programação concorrente. Após baixar, instale o pacote, dando um duplo-clique no arquivo e seguindo as instruções, se houver.Após a instalação, no menu TOOLS > PACKAGE MANAGER ( FERRAMENTAS > PACKAGE MANAGER em português ) o pacote deve aparecer, como na ilustração abaixo:


Com o pacote instalado, precisamos informar ao programa de link edição do Dev-C que estamos usando uma biblioteca externa à padrão. Para fazer isto, vá até o menu TOOLS e em COMPILER OPTIONS marque a caixa Add theese commands to the linker command line insira o comando " -lpthreadGC2 "(Sem aspas) na caixa logo abaixo. Pressione OK.( Se a sua IDE estiver em Português, a sequência é : Ferramentas > Opções do Compilador >Marque a opção Adicionar estes comandos à linha de comando do linker > Na caixa abaixo insira "-lpthreadGC2" (Sem aspas)).


Feito isto você está apto á começar a usar as bibliotecas. No entanto, as funções para manipular os semáforos são um pouco diferentes das funções aprendidas durante a aula de Sistemas Operacionais.

Para Criar um semáforo, utilizamos o tipo de dado sem_t. Ex:

#include <semaphore.h>

sem_t meuSemaforo;

Para Inicializar um semáforo, utilizamos a função sem_init, que recebe como parâmetro uma referência(&) ao semáforo criado.Em seguida é passado o valor da variável pshared, que se for um numero positivo, permite que o semáforo seja compartilhado por outros processos.E finalmente o valor inicial do nosso semáforo.(No exemplo do consumidor/produtor, os semáforos eram inicializados com uma atribuição.Neste método, precisamos executar a função sem_init, á fim de inicializar a variável interna)Ex:

sem_init (&meuSemaforo,0,1); (semáforo binário, não compartilhado entre processos. È geralmente utilizado como o semaforo de Exclusão Mútua (mutex)).

sem_init (&meuSemaforo,0,10);(semáforo inteiro, não compartilhado entre processos. È geralmente utilizado como contador).

Obs : Como utilizaremos o modelo de threads concorrentes , não compartilhamos os semáforos com outros processos. Caso utlilizassemos o modelo de processos concorrentes, teriamos que setar a  propriedade pshared para 1)

Para utilizar a função P(s), descrita no conteúdo de sistemas operacionais, utilizamos a equivalente sem_wait, que recebe como parâmetro a referência(&) para o semáforo. Esta operação testa se o semáforo é maior que 0. Se for, ela decrementa o valor do semáforo e entra no código da Região Crítica.Ex:

sem_wait(&meuSemaforo);

Para utilizar a função V(s), descrita no conteúdo de sistemas operacionais, utilizamos a equivalente sem_post, que recebe como parâmetro a referência(&) para o semáforo. Esta operação quando executada, incrementa o valor do semáforo, e abre o sinal para que outros processos/threads executem a sua Região Crítica.Ex:

sem_post(&meuSemáforo);

Bom, com estas informações já daria para implementar o código do produtor/consumidor sem problemas.
Porém, ainda não implementamos programação multithread para poder testar nossos códigos. Para isto, utilize a biblioteca <pthread.h>.

O tipo de dado para se declarar uma thread é pthread_t. Ex:

#include <pthread.h>

pthread_t minhaThread;

Para criar e inicializar a thread, utilizamos a função pthread_create que contém 4 argumentos que, devido á sua complexidade, serão tratados individualmente. Ex:

pthread_create(&minhaThread, NULL, minhaFuncao , (void *) &meuArgumento);
  1. O primeiro argumento é uma  referência(&) à thread préviamente declarada.(&minhaThread)
  2. O segundo arrgumento é um ponteiro que define os atributos da thread. Para valores padrão, NULL deve ser passado (NULL)
  3. O terceiro argumento é a função que você irá passar para a thread que se criará e executará à partir de agora.Para o exemplo consumidor/produtor é de suma importancia que sua função retorne um ponteiro de void.(explicado melhor adiante)Neste parametro voce nao deve incluir nada além do nome da função (que ja deve estar declarada, né?) (minhaFuncao).
  4. O quarto parâmetro está relacionado aos parâmetros que estaremos passando para nossa função.Não irei muito além neste ponto, pois para entender este argumento você deve entender sobre troca de mensagens entre processos e isto é uma longa história(que veremos com o professor Botega mais adiante) Aqui passaremos uma referência(&) para um inteiro qualquer (que você também já declarou, né?) que servirá como identificador para nossa thread, junto com um casting(coerção) de tipo para ponteiro nulo ((void *) &meuArgumento)
Se você foi um bom menino e seguiu meus passos até aqui, você já possui uma função assim:

void * minhaFuncao (int * meuArgumento)
{
      blablablá.
          sem_wait(&meuSemaforo)
                   região crítica;
          sem_post(&meuSemáforo)
}

E também possui uma main() mais ou menos assim :

void main ()
{
       int meuArgumento1=1,meuArgumento2=2;
       sem_init(&meuSemaforo,0,1);
       pthread_t minhaThread1,minhaThread2;
       pthread_create(&minhaThread1, NULL, minhaFuncao , (void *) &meuArgumento1);
        pthread_create(&minhaThread2, NULL, minhaFuncao , (void *) &meuArgumento2);
}


Se voce entendeu meu raciocinio até aqui, conseguiu compilar tudo, porém quando executa o programa executa e fecha rapidamente, pensa comigo.
Se todo processo tem pelo menos uma unica thread, e esta thread principal termina sua execução, o processo acaba ,certo? Se um processo acaba, ele acaba com todas as coitadinhas que estavam lá em seu laço infinito trabalhando. Culpa de quem? Culpa do seu S.O. que nem faz ideia do que você estava fazendo dentro de seu processo. Ele não conhece suas threads.
 Quando nosso programa chega ao fim da main é isto que acontece. O processo todo termina e nossas threads vão pro saco. Para que isto não ocorra nós fazemos uma grande P.O.G. (Programação Orientada à Gambiarra, para os não íntimos). Simplesmente falamos para o nosso processo - Ei, essa sua thread principal (main()) já era, pegue uma de nossas threads para continuar com o programa.
E utilizando o seguinte comando, ele atende nosso pedido:

pthread_exit(NULL);


Sendo assim, sua main() deve estar parecida com isto:


void main ()
{
       int meuArgumento1=1,meuArgumento2=2;
       sem_init(&meuSemaforo,0,1);
       pthread_t minhaThread1,minhaThread2;
       pthread_create(&minhaThread1, NULL, minhaFuncao , (void *) &meuArgumento1);
       pthread_create(&minhaThread2, NULL, minhaFuncao , (void *) &meuArgumento2);

       pthread_exit(NULL);
}

Bom, felizmente é isto que posso fazer por vocês por hora, pois mais do que isto seria contribuir para a retirada do seu diploma de bacharelado. E a intenção não é esta, e sim promover o conhecimento mútuo, pois eu também poderei e vou, precisar de vocês adiante.
Também espero poder contar com vocês para a divulgação do blog, bem como a avaliação de vocês nesta página. Comentários serão bem-vindos e respondidos.

Um grande abraço










7 comentários:

  1. Também pode se usar
    void *produtor(void * arg) ....
    e criar a tread

    pthread_create(&tid1, NULL, produtor , NULL);

    no meu caso só funciona assim ultimo parâmetro com NULL

    ResponderExcluir
  2. Uma apostila com exemplo quase da forma que estamos acostumados a programar nas aulas da facul

    http://www.ic.unicamp.br/~islene/mo806/prod-cons/prod-cons.pdf

    ResponderExcluir
  3. Sim Tales, o ultimo parametro da função refere-se aos parametros correspondentes ao retorno da sua função criada. Se tua função retorna ponteiro de nulo, NULL é aceito como uma constante pelo compilador, não sendo necessário o casting de tipo para tratar este retorno da mesma.

    ResponderExcluir
  4. Obrigado! Resolveu meu problema.

    ResponderExcluir
  5. Mtoooo Obrigada,
    Esse post foi de grande ajuda!!!
    Parabéns!!!

    ResponderExcluir
  6. Grande contribuição com os bacharelandos aprendizes! Obrigado!

    ResponderExcluir