Programación con Threads baixo Linux

De Wiki do Ciclo ASIR do IES de Rodeira
Saltar á navegación Saltar á procura

Programación con Threads Baixo Linux

En Linux existen moitas librerías que implementan os threads de execución xa que, a diferencia dos procesos (fork), non forman parte do kernel do sistema. Probablemente a librería de threads máis utilizada sexa a librería pthread que é a que imos a estudiar.

Para utilizar a librería pthread é necesario incluir o ficheiro de cabeceira pthread.h (#include <pthread.h>) e cando se compile o programa enlazalo coa librería (-lpthread).

As funcións de manexo de threads son as seguintes:

Creación do Thread

int pthread_create (pthread_t *thread, pthread_attr_t *attr, 
                    void *(*start_routine)(void *), void * arg);

Esta función crea un novo thread de execución que levará a cabo a función start_routine ó mesmo tempo que o thread principal. A función start_routine debe ter o seguinte prototipo:

void * start_routine (void *param);

O parámetro arg é a información que se lle pasará como parámetro á función start_routine. Si a rutina non precisa de ningún argumento, se poñerá NULL.

O parámetro thread é a dirección de memoria dunha variable de tipo pthread_t onde se almacenará a información do novo thread creado.

O parámetro attr contén os atributos que vai ter o novo thread (ver as funcións de manexo de atributos). Se queremos os atributos por defecto, poñeremos NULL

Facer que o thread principal espere a que rematen os threads en execución:

int pthread_join (pthread_t th, void **pthread_return);

A función pthread_join fai que a función que o chama (o thread principal) espere pola finalización dos threads secundarios antes de rematar, de xeito que poda liberar os seus recursos.

O parámetro th é o thread polo que se vai esperar a que finalice.

O parámetro pthread_return almacenará o valor devolto polo thread finalizado.

Finalizar a execución do thread actual

void pthread_exit (void *retval);

Esta función provoca a finalización do thread en execución devolvendo o valor retval. É o equivalente a escribir return retval.

'Independizar' a execución do fío do que o creou (garantizar que o fío libere os seus recursos o finalizar)

int pthread_detach (pthread_t th);

Esta función fai que o thread th se execute de xeito independiente do thread principal, e decir, que unha vez finalizado liberará os seus recursos por si mesmo. A un thread detached con esta función non se lle poderá facer un pthread_join pra esperar pola súa finalización.

Manexo dos atributos do thread que se vai a crear

int pthread_attr_init (pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int schedpolicy);
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *schedpolicy);
int pthread_attr_setschedparam(pthread_attr_t *attr, int schedparam);
int pthread_attr_getschedparam(pthread_attr_t *attr, int *schedparam);
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);

Para poñer os atributos a un thread, se debe encher primeiro unha estructura de tipo pthread_attr_t e logo pasarllo como argumento á función pthread_create, si a esta función lle pasamos NULL como atributo o thread collerá os atributos por defecto. A función pthread_attr_init inicializa o parámetro attr cos atributos por defecto dos threads, mentras que pthread_attr_destroy elimina os atributos que se crearon (en Linux non ten efecto). Pra manipular os distintos atributos que teñen os threads utilízanse o resto das funcións, sendo as funcións que comenzan por pthread_attr_set pra poñer atributos e as que comenzan por pthread_attr_get pra saber que atributos ten. Os atributos que se poden manipular son:

  • detachstate : controla si o thread se crea nun estado no que se pode facer un pthread_join (PTHREAD_CREATE_JOINABLE) ou en estado desenganchado (como si lle fixeramos un pthread_detach) (PTHREAD_CREATE_DETACHED). O valor por defecto é PTHREAD_CREATE_JOINABLE.
  • schedpolicy: controla a política de planificación do proceso (sheduling) vinculados ó thread creado. Pode ser SCHED_OTHER (planificación normal), SCHED_RR (round robin ou roubo de ciclo) ou SCHED_FIFO (planificación FIFO, tamén chamada FCFS (First Comed First Served)). O valor por defecto é SCHED_OTHER
  • schedparam: conten os parámetros da política de planificación de procesos empregada no thread, normalmente a súa prioridade de execución. Por defecto a prioridade é 0 é unicamente ten sentido coas políticas SCHED_FIFO e SCHED_RR.
  • inheritsched: Indica si as políticas de planificación e os seus parámetros se collen do proceso que lanza o thread (PTHREAD_INHERIT_SCHED) ou dos atributos indicados (schedpolicy e schedparam) no momento de crear o thread (PTHREAD_EXPLICIT_SCHED). Por defecto é PTHREAD_EXPLICIT_SCHED.
  • scope: O único valor soportado en Linux é PTHREAD_SCOPE_SYSTEM que indica que os threads competirán co resto dos procesos do sistema pra utilizar a CPU. O outro valor especificado polo estándar pero non soportado en Linux é PTHREAD_SCOPE_PROCESS que indica que os threads únicamente compiten co resto de threads do proceso en funcionamento.


Mutex, Semáforos e Condicións de Sincronización

Supoñamos que temos un programa que lanza dous fíos de execución para atender conexións e vender entradas pra un espectáculo e que inicialmente temos 100 entradas. Deste xeito poderemos atender ós clientes de dous en dous.

Para vender as entradas cada fío comproba si quedan entradas (inicialmente temos 100). Si quedan, realiza a venta restándolle 1 ó número de entradas que quedan por vender.

A primeira vista este algoritmo é correcto, pero supoñamos que queda 1 entrada por vender e solicitan unha compra dous clientes ó mesmo tempo. Cada thread atenderá a un cliente; en primeiro lugar comprobarán si quedan entradas, vendo ambos threads que queda unha entrada e procedendo a efectuar a venta.

¡ Como resultado venderase unha entrada de máis !.

Este tipo de situacións reciben o nome de condicións de competencia ou condicións de carreira (racing condition), xa que varios procesos compiten por un recurso. Para solucionalas os threads proporcionan Mutex, Semáforos e as Condicións de Sincronización.

Mutex

Os mutex permiten bloquear unha parte do código, de xeito que ningún outro fío de execución poda acceder a él mentras non se libere o bloqueo. Deste xeito, si os threads bloquean o código que comproba o número de entradas que quedan e lo liberan despois de realizada a venta, obligamos a que primeiro execute a parte crítica un proceso e logo o outro.

As funcións de manexo de mutex son as seguintes:

   pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
   pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
   pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
   int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_trylock(pthread_mutex_t *mutex);
   int pthread_mutex_unlock(pthread_mutex_t *mutex);
   int pthread_mutex_destroy(pthread_mutex_t *mutex);

Un mutex é un dispositivo de exclusión mutua útil pra protexer estructuras de datos compartidas de modificacións concurrentes, e para implementar rexións críticas e monitores.

Os mutex teñen dous estados posibles: unlocked (libre, non ocupado por ningún thread) ou locked (ocupado por un thread). Un mutex nunca pode estar ocupado por máis de un thread.

Si un thread intenta ocupar un mutex xa ocupado, quedará a espera de que o thread que ten o mutex o libere.

A función pthread_mutex_init inicializa o obxecto mutex indicado polo parámetro mutex de acordo cos atributos indicados no parametro mutexattr. Si o parámetro mutexattr é NULL, utilizaránse atributos por defecto.

A implementación de Linux dos Threads (pthreads) únicamente soporta un atributo para os mutex: O tipo (Mutex Kind). Os mutex poden ser "fast", "recursive" ou "error checking" , indicando cando o mutex pode ser volto a ocupar por un thread que o ten xa ocupado (recursive). O tipo error checking verifica que o thread que quita o bloqueo sexa o mesmo que o puxo; nos outros tipos un thread pode quitar o bloqueo posto por un thread distinto. Por defecto o tipo é "fast".

As variables de tipo pthread_mutex_t poden ser inicializadas tamén de forma estática mediante as constantes PTHREAD_MUTEX_INITIALIZER (fast mutex), PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP (recursive mutex) e PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (error checking mutex).

A función pthread_mutex_lock ocupa o mutex indicado como parámetro. Si o mutex non está ocupado por outro thread, o ocupa e a continúa a execución, si o mutex xa o ten ocupado outro thread, suspende a execución do thread ata que estea dispoñible.

Si o mutex xa está ocupado polo thread que intenta facer un novo pthread_mutex_lock, o comportamento depende do tipo de mutex. Si é fast, o thread queda a espera de que o mutex sexa liberado, o que producirá un dead lock (bloqueo indefinido); si é error checking a función devolverá o código de erro EDEADLK e si é recursive, o thread reocupa o mutex devolvendo o número de veces que o ten ocupado; para liberar o mutex será necesario facer o mesmo número de chamadas a pthread_mutex_unlock que veces se teña ocupado.

pthread_mutex_trylock é igual que pthread_mutex_lock, pero non bloquea o thread si o mutex está ocupado por outro, se non que devolve o código de erro EBUSY.

pthread_mutex_unlock "libera" o mutex ocupado polo thread. Si o mutex é fast , sempre se liberará pero si é recursive decrementará a conta de ocupación do mutex, liberándoo si é 0. No tipo error_check se verifica que o mutex pertence realmente o thread que intenta liberalo, e si non é así devolve un código de erro, os outros tipos permiten liberar mutex que non lles pertencen, sendo esta unha característica non portable e que non se debe utilizar (en realidade é un bug).

pthread_mutex_destroy destrúe un obxecto mutex liberando todos os recursos que teña ocupados. Únicamente se poderán destruir mutex que non estean ocupados por ningún thread. (Como en Linux os mutex non ocupan recursos, esta función en realidade non fai nada).

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(pthread_mutexattr_t  *attr,int kind);
int pthread_mutexattr_gettype(const   pthread_mutexattr_t *attr, int *kind);
  • pthread_mutexattr_init inicializa o obxecto mutex attr e o inicializa cos valores por defecto.
  • pthread_mutexattr_destroy destrúe un obxecto que ten atributos de un mutex. En Linux esta función non ten ningún efecto.
  • pthread_mutexattr_settype e pthread_mutexattr_gettype sirven pra poñer e pra obter o tipo de mutex.

Os atributos poden ser PTHREAD_MUTEX_FAST_NP, PTHREAD_MUTEX_RECURSIVE_NP ou PTHREAD_MUTEX_ERRORCHECK_NP.

Semáforos

Outro mecanismo que poden utilizar os threads pra protexer seccións de código son os semáforos.

Un semáforo é similar a un mutex cun contador. As operacións principais son:

  • esperar a que o contador sexa distinto de 0 e restar 1 o contador antes de continuar
  • sumar 1 o contador

Si o contador é 0 indicará que o thread non pode acceder a sección crítica, e si é >0, poderán acceder tantos threads como valor teña o contador. As funcións de manexo de semáforos son:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *sval);
int sem_destroy(sem_t *sem);

sem_init inicializa o semáforo sem, poñendo a súa conta inicial a value. O argumento pshared indica cando o semáforo é local ó proceso actual (0) ou compartido entre varios procesos (distionto de 0). Linux únicamente soporta semáforos locais ó proceso actual (pshared debe ser igual a 0).

sem_wait suspende a execución do thread mentras a conta do semáforo sexa 0. Cando a conta deixe de ser 0, restarálle 1 e continuará a execución.

sem_trywait é similar a sem_wait pero en lugar de suspender a execución devolverá o erro EAGAIN

sem_post incrementará a conta do semáforo que se lle indique.

sem_getvalue devolverá a conta do semáforo sem en sval.

sem_destroy elimina o semáforo que se lle indique.

Condicións de Sincronización

Unha Condición de Sincronización permite suspender a execución de un thread menstras algúns datos compartidos entre varios threads non cumpran un requisito.

As operacións básicas nas condicíóns de sincronización son: Sinalizar unha Condición (indicar que o requisito se cumpre), e Esperar pola Condición (esperar a que outro thread sinalice a condición ).

Unha condición de sincronización sempre está asociada cun mutex pra evitar a condición de competencia que se produce si un thread vai a esperar por unha condición xusto ó mesmo tempo que outro thread a sinaliza, sin darlle a tempo de esperar por ela.

As funcións de manexo de condicións de sincronización son as seguintes:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, 
                           const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_init inicializa a variable de condición cond usando os atributos especificados no parámetro cond_attr, ou os atributos por defecto si o parámetro é NULL. A implementación en Linux dos threads non soporta atributos para as condicións, de xeito que cond_attr é sempre ignorado. As variables de tipo pthread_cond_t tamén poden ser inicializadas estáticamente mediante a constante PTHREAD_COND_INITIALIZER.

pthread_cond_signal fai que continúe ún dos threads que están a espera da variable que se lle pasa como parámetro, pero non se sabe cal (pode ser calquera dos que están en espera pra esa condición).

pthread_cond_broadcast fai que continúen todos os threads que estan a esperar pola condición especificada como parámetro.

pthread_cond_wait desbloquea o mutex e suspende o thread á espera de que se cumpla a condición indicada (cond). Antes de chamar a esta función é necesario bloquea-lo mutex chamando á función pthread_mutex_lock. Antes de voltar, esta función volve a bloquea-lo mutex de xeito automático. Para evitar a condición de competencia xa comentada, o thread que sinalice a condición con pthread_cond_signal debe bloquea-lo mutex antes.

pthread_cond_timedwait traballa do mesmo xeito que pthread_cond_wait, pero su a condición non se cumple no tempo indicado no parámetro abstime, a función devolve o erro ETIMEDOUT. O tempo ven expresado no formato da función time (número de segundos dende o 1 de xaneiro de 1970).

pthread_cond_destroy libera unha variable de condición e todos os recursos que poida ter asociados. Na implementación de Linux non ten ningún efecto.

Tanto a función pthread_cond_wait como pthread_cond_timedwait son 'puntos de cancelación'.

Vexamos un caso práctico:

Consideremos dúas variables compartidas x e y protexidas polo mutex mut e a condición cond que será sinalizada cando x sexa maior que y:

int x,y;

pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

Os threads que esperan, incluirán o seguinte código:

// Bloqueo a sección crítica
pthread_mutex_lock (&mut);
// En realidade bastaría un if, pero si hai varios threads...
while(x<=y) 
{
  // Esperamos o sinal..
  pthread_cond_wait(&cond,&mut);
}

/* Aquí traballaríamos con '''x''' e con '''y''' */

// Os outros threads poden continuar...
pthread_mutex_unlock(&mut);

O thread que sinaliza a condición tería o código seguinte:

// Bloqueo a sección crítica
pthread_mutex_lock(&mut);

/* Aquí traballaríamos con '''x''' e con '''y''' */

// Sinalo a condición. Se únicamente tivera 1 thread en espera bastaría pthread_cond_signal
if (x>y) pthread_cond_broadcast(&cond);
// Libero o mutex
pthread_mutex_unlock(&mut);

Se quixeramos que a espera tivera un timeout de 5 segundos:

struct timeval now;
struct timespec timeout;
int retcode;

pthread_mutex_lock(&mut);
gettimeofday(&now);
timeout.tv_sec=now.tv_sec+5;
timeout.tv_nsec=now.tv_usec*1000;
retcode=0;
while ((x<=y) && (retcode != ETIMEDOUT)) 
{
  retcode=pthread_cond_timedwait(&cond,&mut,&timeout);
}
if (retcode == ETIMEDOUT) 
{
  /* Error, acabouse o tempo (pasaron os 5 segundos) */
} else 
{
  /* Traballar coas variables '''x''' e '''y''' */
}
pthread_mutex_unlock(&mut);

Cancelación de Threads

A cancelación é un mecanismo polo que un thread pode provocar a finalización de outro enviándolle unha petición de finalización. Dependendo do xeito de traballar do outro thread, pode ignora-la petición, finalizar inmediatamente ou esperar a alcanzar un punto de cancelación. Si o thread atende inmediatamente a petición remata devolvendo o código PTHREAD_CANCELED.

Os puntos de cancelación son puntos na execución donde se comproba si hai peticións de cancelación pendentes. As seguintes funcións teñen puntos de cancelación: pthread_join pthread_cond_wait pthread_cond_timedwait pthread_testcancel sem_wait sigwait

As funcions de cancelación de thread son as seguintes:

int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state, int *oldstate);
int  pthread_setcanceltype(int type,int *oldtype);
int  pthread_testcancel(void);

pthread_cancel envía unha petición de cancelación ó thread indicado como argumento.

pthread_setcancelstate cambia o estado de cancelación do thread, é decir indica cando se ignorarán as sinais de cancelación e cando non. O parámetro state pode ser PTHREAD_CANCEL_ENABLE pra permitir a cancelación ou PTHREAD_CANCEL_DISABLE pra non permitila. Si o parámetro oldstate non é NULL, devolverá o estado de cancelación previo.

pthread_setcanceltype cambia o tipo de resposta ás peticións de cancelación, e pode ser PTHREAD_CANCEL_ASYNCHRONOUS (pra cancelación inmediata) ou PTHREAD_CANCEL_DEFERRED (esperar ó seguinte punto de cancelación). Si o parámetro oldtype non é NULL almacenará o tipo de cancelación anterior.

pthread_testcancel únicamente sitúa un punto de cancelación no lugar en que se chame.

Os threads créanse por defecto coa cancelación activa co tipo PTHREAD_CANCEL_DEFERRED

Envío e Xestión de Sinais.

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);
int pthread_kill(pthread_t thread, int signo);
int sigwait(const sigset_t *set, int *sig);

pthread_sigmask cambia o conxunto de sinais o que vai a respostar o thread. Si o parámetro oldmask non é NULL, almacenará o conxunto vello de sinais. Si how é SIG_SETMASK o conxunto de sinais se inicializa a newmask, si é SIG_BLOCK engádense as sinais especificadas en newmask e si é SIG_UNBLOCK quítanse as sinais especificadas en newmask do conxunto de sinais ós que vai respostar o thread. Os conxuntos de sinais manipúlanse coas funcións:

int sigemptyset(sigset_t *conxunto); // (vacía o conxunto de sinais indicado).
int sigfillset(sigset_t *conxunto);  // (inclúe todos os sinais existentes no conxunto indicado).
int sigaddset(sigset_t *conxunto, int numsinal); // (engade o sinal indicado ó conxunto).
int sigdelset(sigset_t *conxunto, int numsinal); // (elimina o sinal indicado do conxunto).
int sigismember(const sigset_t *conxunto, int numsinal); // (indica si o sinal indicado pertence o conxunto).

pthread_kill envía o sinal signo o thread especificado.

sigwait suspende o thread hasta que recibe unha sinal do conxunto indicado en set, almacenando entonces o sinal recibido en sig. Se ignorará calqueira función asociada o sinal.

Exemplos

Creación dun Thread

Este programa crea dous fíos de execución que visualizan o mesmo tempo os números dende 0 a 1000000000. Pra distinguir o primeiro thread visualizará THREAD PPAL: <nº> e o thread que lanzamos visualizará THREAD 1: <nº>:

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

void *funcion(void *arg);

// Función que vai a ser executada como un thread secundario.
// Visualiza números entre 0 e 1000000000
//
void *funcion(void *arg) 
{
  unsigned int x;

  x=0;
  while(x<=1000000000) 
  {
    printf("THREAD 1: %d\n",x);
    x++;
  }
  return NULL;
}

// Programa Principal. Visualizará números entre 0 e 1000000000
// despois de lanzar outro thread de execución.
void  main(void)  
{
  // Variable para almacenar a información do thread creado.
  // O thread principal é a función main().
  pthread_t thr;

  // Variable pra almacenar o valor devolto polo thread executado.
  void *retval;
  
  unsigned int x;
  

  // Creación do novo thread.
  pthread_create(&thr,NULL,funcion,NULL);
  // A partir deste momento, 'funcion' executarase ó mesmo tempo.
  // que o seguinte código....
  x=0;
  while(x<=1000000000) 
  {
    printf("THREAD PPAL: %d\n",x);
    x++;
  }
  // Esperamos a que finalice o thread secundario.
  // (lanzado dende esta función).
  pthread_join(thr,&retval);
}

Sincronización con Mutex

Este programa crea dous fíos de execución que visualizan o mesmo tempo os números dende 0 a 1000000000. Para distinguir o primeiro thread visualizará THREAD PPAL: <nº> e o thread que lanzamos visualizará THREAD 1: <nº>. Utilizaremos un mutex pra facer que primeiro faga toda a conta un thread e logo o outra:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *funcion(void *arg);

// Variable mutex pra facer que un thread espere a que finalice o outro.
// utilizaremos un mutex 'fast'.
pthread_mutex_t   semaforo=PTHREAD_MUTEX_INITIALIZER;

void *funcion(void *arg) 
{
  unsigned int x;

  // Ocupamos o mutex (si non está ocupado).
  pthread_mutex_lock(&semaforo);
  
  x=0;
  while(x<=1000000000) 
  {
    printf("THREAD 1: %d\n",x);
    x++;
  }
  
  // Liberamos o mutex.
  pthread_mutex_unlock(&semaforo);
}

void  main(void)  
{
  // Variable pra almacenar a información do thread creado. .
  // O thread principal será a función main().
  pthread_t thr;
  // Variable pra almacenar o valor devolto polo thread executado.
  void *retval;
  unsigned int x;

  // Creación do novo thread.
  pthread_create(&thr,NULL,funcion,NULL);

  // A partir deste momento, 'funcion' executarase simultáneamente.
  // Ocupamos o mutex (si non está ocupado).
  pthread_mutex_lock(&semaforo);
  x=0;
  while(x<=1000000000) 
  {
    printf("THREAD PPAL: %d\n",x);
    x++;
  }
  // Liberamos o mutex.
  pthread_mutex_unlock(&semaforo);
  // Esperamos a que finalice o thread secundario.
  // (lanzado dende esta función).
  pthread_join(thr,&retval);
}

Sincronización con Semáforos

Este programa crea dous fíos de execución que mercan entradas mentras quedan. Para distinguir o primeiro thread visualizará THREAD 1: e o segundo thread THREAD 2: .

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>
#include <semaphore.h>

int __numEntradas=100000;

sem_t semaforo;

void *funcion(void *arg);
 
// Función que vai a ser executada como un thread secundario.
// Visualiza números entre 0 e 1000000000
//
void *funcion(void *arg) 
{
   while(__numEntradas>0) {
     sem_wait(&semaforo); 
     if (__numEntradas>0) {  
       printf("%s MERCA ENTRADA %d\n",arg,__numEntradas);
       __numEntradas--;
     }
     sem_post(&semaforo);
   }
}
 
// Programa Principal. Visualizará números entre 0 e 1000000000
// despois de lanzar outro thread de execución.
void  main(void)  
{
  // Variable para almacenar a información do thread creado.
  // O thread principal é a función main().
  pthread_t thr,thr1;
 
  // Variable pra almacenar o valor devolto polo thread executado.
  void *retval;
  
  sem_init(&semaforo,0,1);

  // Creación do novo thread.
  pthread_create(&thr,NULL,funcion,"THREAD 1");
  pthread_create(&thr1,NULL,funcion,"THREAD 2");
 
  // Esperamos a que finalice o thread secundario.
  // (lanzado dende esta función).
  pthread_join(thr,&retval);
  pthread_join(thr1,&retval);
  printf("Quedan %d\n",__numEntradas);
}