/* NOTE: compile with -pthread */

#include <stdlib.h>

#include <stdio.h>

#include <pthread.h>

/* uncomment this for only one consumer */

//#define USE_TWO_CONSUMER

/* size of stream buffer */

#define BUFFER_SIZE 10

typedef struct stream_t

{

/* used to lock/unlock write and read function */

pthread_mutex_t buffer_mutex ;

/* used to wait for empty condition */

pthread_cond_t empty_cond ;

/* used to wait for full condition */

pthread_cond_t full_cond ;

int buffer [ BUFFER_SIZE ] ; /* buffer to save data temporary */

int head ; /* head is the start position of data */

int tail ; /* tail is the end position of data */

int empty ; /* emtpy = 1 then empty, else 0 */

int full ; /* full = 1 then full, else 0 */

} stream_t ;

int stream_init ( stream_t * stream )

{

for ( int i = 0 ; i < BUFFER_SIZE ; i ++ )

stream -> buffer [ i ] = 0 ;

stream -> head = 0 ;

stream -> tail = 0 ;

stream -> empty = 1 ;

stream -> full = 0 ;

/* init mutex */

pthread_mutex_init ( & stream -> buffer_mutex , NULL ) ;

/* init empty condition */

if ( pthread_cond_init ( & stream -> empty_cond , NULL ) != 0 )

{

printf ( "ERROR: condition variable

" ) ;

return - 1 ;

}

/* init full condition */

if ( pthread_cond_init ( & stream -> full_cond , NULL ) != 0 )

{

printf ( "ERROR: condition variable

" ) ;

return - 1 ;

}

return 0 ;

}

/* write to stream */

int stream_write ( stream_t * stream , int * buffer , int len )

{

/* we can only write when the stream is empty,

so we wait until the condition accures. */

pthread_mutex_lock ( & stream -> buffer_mutex ) ;

while ( stream -> empty != 1 )

{

/* unlocks the mutex and goes to sleep, gets woken

up when someone invokes 'pthread_cond_signal' and

locks the mutex. */

pthread_cond_wait ( & stream -> empty_cond , & stream -> buffer_mutex ) ;

}

/* integers written */

int count = 0 ;

/* when we wrote all elements we are finished */

while ( count < len )

{

/* write into stream */

stream -> buffer [ stream -> head ] = buffer [ count ] ;

/* increase head, if it is equal to BUFFER_SIZE

'rotate' it back to 0. */

stream -> head += 1 ;

if ( stream -> head == BUFFER_SIZE )

stream -> head = 0 ;

/* then increase count */

count += 1 ;

/* If head and tail are equal, we are full */

if ( stream -> head == stream -> tail )

{

stream -> empty = 0 ; /* it can't be empty */

stream -> full = 1 ; /* but full */

pthread_cond_signal ( & stream -> full_cond ) ; /* signal: we are full */

break ;

}

}

pthread_mutex_unlock ( & stream -> buffer_mutex ) ;

/* return the number of written integers */

return count ;

}

/* read from stream */

int stream_read ( stream_t * stream , int * buffer , int len )

{

pthread_mutex_lock ( & stream -> buffer_mutex ) ;

/* Wait until full */

while ( stream -> full == 0 )

pthread_cond_wait ( & stream -> full_cond , & stream -> buffer_mutex ) ;

/* integers read */

int count = 0 ;

while ( count < len )

{

/* read last element */

buffer [ count ] = stream -> buffer [ stream -> tail ] ;

/* increase tail and 'rotate' if necessary */

stream -> tail += 1 ;

if ( stream -> tail == BUFFER_SIZE )

stream -> tail = 0 ;

/* increase count */

count += 1 ;

if ( stream -> tail == stream -> head )

{

stream -> empty = 1 ; /* we must be empty */

stream -> full = 0 ; /* and we can't be full */

pthread_cond_signal ( & stream -> empty_cond ) ; /* signal: we are emtpy */

break ;

}

}

pthread_mutex_unlock ( & stream -> buffer_mutex ) ;

return count ;

}

void * producer ( void * arg )

{

stream_t * stream = ( stream_t * ) arg ;

for ( int i = 1 ; i <= 32000 ; i ++ )

{

/* writes one integers to the stream, at some point the

stream is full, then this thread will block until it

is empty. */

stream_write ( stream , & i , 1 ) ;

printf ( "PRODUCER: (write) %d

" , i ) ;

}

printf ( "PRODUCER: finished

" ) ;

return NULL ;

}

void * consumer1 ( void * arg )

{

stream_t * stream = ( stream_t * ) arg ;

int counter = 0 ;

while ( 1 )

{

int buffer ;

stream_read ( stream , & buffer , 1 ) ;

printf ( "CONSUMER1: (read) %d

" , buffer ) ;

counter += 1 ;

/* if we use two consumer, only

read the half of all items */

#ifdef USE_TWO_CONSUMER

if ( counter == 16000 ) break ;

#endif

#ifndef USE_TWO_CONSUMER

if ( counter == 32000 ) break ;

#endif

}

printf ( "CONSUMER1: finished

" ) ;

return NULL ;

}

#ifdef USE_TWO_CONSUMER

void * consumer2 ( void * arg )

{

stream_t * stream = ( stream_t * ) arg ;

int counter = 0 ;

while ( 1 )

{

int buffer ;

stream_read ( stream , & buffer , 1 ) ;

printf ( "CONSUMER2: (read) %d

" , buffer ) ;

counter += 1 ;

#ifdef USE_TWO_CONSUMER

if ( counter == 16000 ) break ;

#endif

#ifndef USE_TWO_CONSUMER

if ( counter == 32000 ) break ;

#endif

}

printf ( "CONSUMER2: finished

" ) ;

return NULL ;

}

#endif

int main ( )

{

/* for tesing stream */

/*

stream_t stream;

stream_init(&stream);

// array1 is written into the stream, whereas

// array2 is used to receive the data.

int array1[12] = { 1, 2, 3 ,4 , 5 ,6 ,7 , 8,9 , 10, 11, 12};

int array2[12];

printf("written integers %d

", stream_write(&stream, array1, 12));

printf("read integers %d

", stream_read(&stream, array2, 12));

for(int i = 0; i < 12; i++)

printf("%d ", array2[i]);

printf("

");

*/

stream_t stream ;

stream_init ( & stream ) ;

pthread_t thread_producer ;

if ( pthread_create ( & thread_producer , NULL , producer , & stream ) != 0 )

{

printf ( "ERROR: thread

" ) ;

return - 1 ;

}

pthread_t thread_consumer1 ;

if ( pthread_create ( & thread_consumer1 , NULL , consumer1 , & stream ) != 0 )

{

printf ( "ERROR: thread

" ) ;

return - 1 ;

}

#ifdef USE_TWO_CONSUMER

pthread_t thread_consumer2 ;

if ( pthread_create ( & thread_consumer2 , NULL , consumer2 , & stream ) != 0 )

{

printf ( "ERROR: thread

" ) ;

return - 1 ;

}

#endif

pthread_join ( thread_producer , NULL ) ;

pthread_join ( thread_consumer1 , NULL ) ;

#ifdef USE_TWO_CONSUMER

pthread_join ( thread_consumer2 , NULL ) ;

#endif

return 0 ;