Original Notes 2013-05-05
In a comment, I noted:
You have wrapper control, so when the writer reaches the end of the array, the next record is placed at the beginning, but you have no control over the filling, so if producers produce faster than consumers consume, producers overwrite unread data. You must ensure that the Fifo array Fifo not full.
Nick Rosencrantz remarked:
You are correct, increasing the FIFO size to 100 corrects the error.
In fact, increasing the FIFO size does not correct the error; he just avoids the mistake longer. You need to keep track of whether the record pointer (index) will catch up with the read pointer, and either not add or delay the addition of a new number until one of the consumers reads the number on the readable pointer so that the space again.
Actual code testing
The code in the question is the code from the exercise verbatim. I'm not sure what the hardware is, but screen shots show that Windows is enabled. I have a Mac. Superficially, which makes code testing tough - one of the files is assembler. However, the code can be easily supplemented by implementations of Unix primitives (POSIX pthread). In fact, the laboratory is well designed; it was very easy to make a simulation. However, you should accept any of the results that I report with a corresponding pinch of salt - the Mac is a completely different machine.
I prefer functions declared before they are defined or used.
#include <unistd.h> extern void fatal_error(char * msg); extern void Sleep (int n); extern void Signal(int *sem); extern void Wait(int *sem); extern void PutFifo(int tal); extern int GetFifo(void); extern void Producer(struct Prod * prodstruct); extern void Consumer(int * tal);
while (1); in fatal_error() - wait; I would rather use pause() . However, this has never been done, so it may not matter.
The necessary oslab_*() primitives can be modeled using POSIX pthreads trivially:
#include <pthread.h> #include <time.h> static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; void oslab_begin_critical_region(void) { pthread_mutex_lock(&mtx); } void oslab_end_critical_region(void) { pthread_mutex_unlock(&mtx); } int oslab_create_thread(int (*thread_function)(void *), void *data, unsigned int *stack) { typedef void *(*ThreadMain)(void *); static int threadnum = 0; pthread_t pth; pthread_attr_t pat; pthread_attr_init(&pat); pthread_attr_setdetachstate(&pat, PTHREAD_CREATE_DETACHED); if (pthread_create(&pth, &pat, (ThreadMain)thread_function, data) != 0) { char buffer[128]; sprintf(buffer, "Failed to create thread with stack %p\n", stack); fatal_error(buffer); } return ++threadnum; } void oslab_idle(void) { pause(); } void oslab_yield(void) { struct timespec rqtp = { .tv_sec = 0, .tv_nsec = 1000000 }; // 1 millisecond if (nanosleep(&rqtp, 0) != 0) fatal_error("nanosleep failed\n"); } /* end pthread implementation */
All messages are in the annoying format "\nMessage" ; This is a formatted format. Rewrite them all so that the new line is at the end, where the "end of line" character should always be.
When debug printing is enabled, lines should be erased. I used fflush(0) (the equivalent, in this context, fflush(stdout) ) after debugging the prints. An alternative (better) way to do this would be to call setvbuf() in main() to set line buffering.
char buffer[BUFSIZ]; setvbuf(stdout, buffer, _IOLBF, BUFSIZ);
results
With these changes in place (leaving the synchronization primitives - the Wait() , Signal() and oslab_yield() - commented out), all hell breaks:
System starting... Producer 1 is created with thread-ID 1 Producer 2 is created with thread-ID 2 Producer 3 is created with thread-ID 3 Consumer 1 is created with thread-ID 4 Consumer 2 is created with thread-ID 5 Consumer 1 gets Prime 0 Next Prime from producer 1 is 2003 Consumer 2 gets Prime 0 Next Prime from producer 2 is 5003 Next Prime from producer 3 is 8009 Consumer 1 gets Prime 0 Next Prime from producer 1 is 2011 Consumer 2 gets Prime 0 Next Prime from producer 2 is 5009 Consumer 1 gets Prime 0 Next Prime from producer 1 is 2017 Consumer 2 gets Prime 0 Next Prime from producer 3 is 8011 Next Prime from producer 2 is 5011 Consumer 1 gets Prime 0 Next Prime from producer 1 is 2027 Consumer 2 gets Prime 0 Consumer 1 gets Prime 0 Next Prime from producer 3 is 8017 Next Prime from producer 2 is 5021 Next Prime from producer 1 is 2029 Consumer 2 gets Prime 0 Consumer 1 gets Prime 2003 Consumer 2 gets Prime 2029 Next Prime from producer 1 is 2039 Next Prime from producer 3 is 8039 Next Prime from producer 2 is 5023 Consumer 1 gets Prime 8009 Consumer 2 gets Prime 2011
However, if you turn on synchronization primitives (but donβt turn off the debugging code), you get reasonable behavior: the consumer does not try to read the queue before there is data in the queue, and the authors do not try to write to the queue when there is no space in the queue.
System starting... Producer 1 is created with thread-ID 1 Producer 2 is created with thread-ID 2 Producer 3 is created with thread-ID 3 Consumer 1 is created with thread-ID 4 Consumer 2 is created with thread-ID 5 Next Prime from producer 1 is 2003 Next Prime from producer 2 is 5003 Next Prime from producer 3 is 8009 Consumer 1 gets Prime 2003 Consumer 2 gets Prime 5003 Next Prime from producer 1 is 2011 Next Prime from producer 3 is 8011 Next Prime from producer 2 is 5009 Next Prime from producer 2 is 5011 Consumer 1 gets Prime 8009 Consumer 2 gets Prime 2011 Next Prime from producer 1 is 2017 Next Prime from producer 3 is 8017 Consumer 1 gets Prime 8011 Consumer 2 gets Prime 5009 Next Prime from producer 2 is 5021 Next Prime from producer 1 is 2027 Next Prime from producer 3 is 8039 Next Prime from producer 2 is 5023 Consumer 1 gets Prime 5011 Consumer 2 gets Prime 2017 Next Prime from producer 3 is 8053 Next Prime from producer 1 is 2029 Next Prime from producer 2 is 5039
This is to be expected. If you have true multi-core streaming (Intel Core i7), then without synchronization you get all kinds of behavior with an odd ball. With synchronization, everything is calm. I allowed the code to run with the output going to the file. When the results are analyzed, you see one occurrence of each of the numbers 2003..4999, two occurrences of each of the numbers 5003..7993 and three occurrences of each of the prime numbers from 8009 up, which was to be expected.
If you enable the debugging code, you will see more results:
System starting... Producer 1 is created with thread-ID 1 Next Prime from producer 1 is 2003 Producer 2 is created with thread-ID 2 PutFifo: 2003 wraddr = 0 Producer 3 is created with thread-ID 3 Consumer 1 is created with thread-ID 4 GetFifo: 2003 rdaddr = 0 Consumer 2 is created with thread-ID 5 Next Prime from producer 2 is 5003 Next Prime from producer 3 is 8009 Consumer 1 gets Prime 2003 PutFifo: 5003 wraddr = 1 GetFifo: 5003 rdaddr = 1 Consumer 1 gets Prime 5003 Next Prime from producer 1 is 2011 PutFifo: 2011 wraddr = 2 GetFifo: 2011 rdaddr = 2 Consumer 2 gets Prime 2011 PutFifo: 8009 wraddr = 3 Next Prime from producer 2 is 5009 PutFifo: 5009 wraddr = 4 GetFifo: 8009 rdaddr = 3 Consumer 1 gets Prime 8009 Next Prime from producer 3 is 8011 GetFifo: 5009 PutFifo: 8011 rdaddr = 4 Next Prime from producer 1 is 2017 wraddr = 5 Consumer 2 gets Prime 5009 Next Prime from producer 2 is 5011 PutFifo: 5011 wraddr = 6 PutFifo: 2017 wraddr = 7 GetFifo: 8011 rdaddr = 5 Consumer 2 gets Prime 8011 GetFifo: 5011 rdaddr = 6 Next Prime from producer 3 is 8017 Consumer 1 gets Prime 5011 PutFifo: 8017 wraddr = 8 Next Prime from producer 2 is 5021 PutFifo: 5021 wraddr = 9 Next Prime from producer 1 is 2027 PutFifo: 2027 wraddr = 0 GetFifo: 2017 rdaddr = 7 Consumer 1 gets Prime 2017
It shows a wraddr wrap from 9 to 0. Otherwise, it is verbose and uninteresting.
Launch gdb
It is not clear that you can run GDB in a program in the source environment. You are using streaming using homebrew (if you are using the original source oslab_lowlevel_c.c and oslab_lowlevel_asm.s ) and GDB will not know multithreading.
When using the POSIX thread that I used, it would be possible to debug the code using GDB.