-
Notifications
You must be signed in to change notification settings - Fork 6
Add producer-consumer example #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| The code for this producer-consumer example was derived from a homework | ||
| from an earlier year. | ||
| The rest of this note describes how this code was constructed. | ||
|
|
||
| [ NOTE: If you use McMini with this, please replace 'sem_' by 'mysem_' in | ||
| partial_solution.c and producer-consumer.c. Otherwise, McMini will | ||
| think that 'sem_' is the real thing, and debug on that. This code | ||
| has already replaced 'sem_' with 'mysem_'. | ||
| I've found that 'mcmini-gdb -m 30 ./producer-consumer' | ||
| suffices to show the bug. ] | ||
|
|
||
| Take the code from hw6 (producer-consumer.c and partial-solution.c), | ||
| and in partial-solution.c, replace the original 'block()' function by | ||
| the variation below: | ||
|
|
||
| void block(sem_t *sem, int blocking_count) { | ||
| while (1) { | ||
| pthread_mutex_lock( &(sem->mutex) ); | ||
| // if someone just posted and the count is back to what it was | ||
| // before I called sem_wait and blocked: | ||
| if (sem->count == blocking_count + 1) { | ||
| pthread_mutex_unlock( &(sem->mutex) ); | ||
| return; | ||
| } | ||
| pthread_mutex_unlock( &(sem->mutex) ); | ||
| sleep(1); // sleep for a second, and check later if count >= 0. | ||
| } | ||
| } | ||
|
|
||
| Finally, you need to set the blocking_count somewhere in your code. | ||
| We will add it to sem_wait. In the original partial_solution.c:sem_wait() | ||
| WE HAD: | ||
| if (sem->count < 0) { | ||
| // We must release lock, or no one can ever call sem_post() for us. | ||
| pthread_mutex_unlock( &(sem->mutex) ); | ||
| block(sem); | ||
|
|
||
| YOU MUST CHANGE IT TO: | ||
| if (sem->count < 0) { | ||
| // We must release lock, or no one can ever call sem_post() for us. | ||
| int blocking_count = sem->count; // This is why we will block. | ||
| pthread_mutex_unlock( &(sem->mutex) ); | ||
| block(sem, blocking_count); | ||
|
|
||
| ALSO CHANGE: | ||
| void block(sem_t *sem); // Used by sem_wait() | ||
| TO: | ||
| void block(sem_t *sem, int blocking_count); // Used by sem_wait() |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||
| FILE = producer-consumer | ||||||||||||||
|
|
||||||||||||||
| CC = gcc | ||||||||||||||
|
|
||||||||||||||
| CPPFLAGS += -I../.. | ||||||||||||||
| CFLAGS += -g3 -O0 | ||||||||||||||
| LDLIBS += -lpthread | ||||||||||||||
|
|
||||||||||||||
| run: $(FILE) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Make
Proposed fix run: $(FILE)
+ ./$(FILE)📝 Committable suggestion
Suggested change
🧰 Tools🪛 checkmake (0.3.2)[warning] 9-9: Target "run" should be declared PHONY. (phonydeclared) 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| # Create executable from producer-consumer.c and partial-solution.c | ||||||||||||||
| $(FILE): $(FILE).c | ||||||||||||||
| $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ \ | ||||||||||||||
| $(FILE).c partial-solution.c $(LDLIBS) | ||||||||||||||
|
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Track The recipe compiles Proposed fix-$(FILE): $(FILE).c
+$(FILE): $(FILE).c partial-solution.c📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| gdb: $(FILE) | ||||||||||||||
| gdb $(FILE) | ||||||||||||||
|
|
||||||||||||||
| vi: | ||||||||||||||
| vi $(FILE).c | ||||||||||||||
|
|
||||||||||||||
| emacs: | ||||||||||||||
| emacs $(FILE).c | ||||||||||||||
|
|
||||||||||||||
| clean: | ||||||||||||||
| rm -f $(FILE) a.out *~ | ||||||||||||||
|
|
||||||||||||||
| # 'make' views $v as a make variable and expands $v into the value of v. | ||||||||||||||
| # By typing $$, make will reduce it to a single '$' and pass it to the shell. | ||||||||||||||
| # The shell will view $dir as a shell variable and expand it. | ||||||||||||||
| dist: | ||||||||||||||
| dir=`basename $$PWD`; cd ..; tar cvf $$dir.tar ./$$dir; gzip $$dir.tar | ||||||||||||||
| dir=`basename $$PWD`; ls -l ../$$dir.tar.gz | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,85 @@ | ||||||||||||||||||||||
| // Remember to compile as: gcc -c -pthread THIS_FILE | ||||||||||||||||||||||
| // This generates a .o file that can be linked to your test program, | ||||||||||||||||||||||
| // myhw.c, which will contain the 'main()' routine. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // The motivation for semaphores is the producer-consumer problem. | ||||||||||||||||||||||
| // See Chapter 31 of ostep.org - | ||||||||||||||||||||||
| // especially page 10: Figure 31.12: Adding Mutual Exclusion (Correctly) | ||||||||||||||||||||||
| // Note that in class, I preferred to write: | ||||||||||||||||||||||
| // pthread_mutex_lock(&mutex); ... pthread_mutex_unlock(&mutex); | ||||||||||||||||||||||
| // while the textbook prefers the following pattern that does the same thing: | ||||||||||||||||||||||
| // sem_t mutex; sem_wait(&mutex); ... sem_post(&mutex); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #include <unistd.h> | ||||||||||||||||||||||
| #include <pthread.h> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Recall that the count for a semaphore has the following interpretation: | ||||||||||||||||||||||
| // If count >= 0, then it's the number of calls to sem_wait() to be allowed. | ||||||||||||||||||||||
| // If count < 0, then it's the number of threads waiting on this semaphore. | ||||||||||||||||||||||
| // I refer to this as a "consistency criterion". The count always tells | ||||||||||||||||||||||
| // you the number of threads that are blocked (that are waiting). | ||||||||||||||||||||||
| // So, sem_wait() essentially does: {count--; if (count < 0) block();} | ||||||||||||||||||||||
| struct sem { | ||||||||||||||||||||||
| int count; | ||||||||||||||||||||||
| pthread_mutex_t mutex; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| typedef struct sem sem_t; | ||||||||||||||||||||||
| // Unlike Java, C/C++ require a semicolon to end the 'struct sem' statement. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| int sem_init(sem_t *sem, int ignore, int init) { | ||||||||||||||||||||||
| sem->count = init; | ||||||||||||||||||||||
| // Anybody who wants to use sem->count should acquire this lock first. | ||||||||||||||||||||||
| pthread_mutex_init(&(sem->mutex), NULL); // See 'man pthread_mutex_init' | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+29
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/sh
set -eu
rg -nP '^\s*int\s+sem_init\s*\(' \
test/producer-consumer/partial-solution.c \
test/producer-consumer/producer-consumer.cRepository: mcminickpt/mcmini Length of output: 354 🏁 Script executed: #!/bin/sh
set -eu
printf '--- partial-solution.c ---\n'
cat -n test/producer-consumer/p_partial-solution.c 2>/dev/null || cat -n test/producer-consumer/partial-solution.c | sed -n '1,80p'
printf '\n--- producer-consumer.c relevant lines ---\n'
cat -n test/producer-consumer/producer-consumer.c | sed -n '1,80p'
printf '\n--- header references ---\n'
rg -n 'sem_init|sem_t' test/producer-consumer -C 2Repository: mcminickpt/mcmini Length of output: 246 🏁 Script executed: #!/bin/sh
set -eu
printf '%s\n' '--- partial-solution.c ---'
cat -n test/producer-consumer/partial-solution.c | sed -n '1,80p'
printf '%s\n' ''
printf '%s\n' '--- producer-consumer.c relevant lines ---'
cat -n test/producer-consumer/producer-consumer.c | sed -n '1,80p'
printf '%s\n' ''
printf '%s\n' '--- header references ---'
rg -n 'sem_init|sem_t' test/producer-consumer -C 2Repository: mcminickpt/mcmini Length of output: 12096 Fix
Proposed fix-int sem_init(sem_t *sem, int ignore, int init) {
+int sem_init(sem_t *sem, int ignore, unsigned int init) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| int sem_post(sem_t *sem) { | ||||||||||||||||||||||
| pthread_mutex_lock( &(sem->mutex) ); | ||||||||||||||||||||||
| sem->count += 1; | ||||||||||||||||||||||
| pthread_mutex_unlock( &(sem->mutex) ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| void block(sem_t *sem); // Used by sem_wait() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| int sem_wait(sem_t *sem) { | ||||||||||||||||||||||
| pthread_mutex_lock( &(sem->mutex) ); | ||||||||||||||||||||||
| sem->count -= 1; | ||||||||||||||||||||||
| if (sem->count < 0) { | ||||||||||||||||||||||
| // We must release lock, or no one can ever call sem_post() for us. | ||||||||||||||||||||||
| pthread_mutex_unlock( &(sem->mutex) ); | ||||||||||||||||||||||
| block(sem); | ||||||||||||||||||||||
| pthread_mutex_lock( &(sem->mutex) ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| pthread_mutex_unlock( &(sem->mutex) ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+22
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Rename the custom semaphore API before running under McMini. The README says McMini requires
📍 Affects 3 files
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| void block(sem_t *sem) { | ||||||||||||||||||||||
| int mycount; | ||||||||||||||||||||||
| while (1) { | ||||||||||||||||||||||
| pthread_mutex_lock( &(sem->mutex) ); | ||||||||||||||||||||||
| mycount = sem->count; | ||||||||||||||||||||||
| if (mycount >= 0) { | ||||||||||||||||||||||
| pthread_mutex_unlock( &(sem->mutex) ); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| pthread_mutex_unlock( &(sem->mutex) ); | ||||||||||||||||||||||
| sleep(1); // sleep for a second, and check later if count >= 0. | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // This is a partial solution. | ||||||||||||||||||||||
| // The above code works correctly when there are two threads, | ||||||||||||||||||||||
| // but when there are three or more threads, a bug will appear. | ||||||||||||||||||||||
| // The bug appears when more than one thread is blocked: | ||||||||||||||||||||||
| // e.g., two threads blocked, one thread ready to post. | ||||||||||||||||||||||
| // In this case, our consistency criterion tells us that 'count == -2'. | ||||||||||||||||||||||
| // Now, suppose some additional thread calls 'sem_post(&mysem)', | ||||||||||||||||||||||
| // causing the 'count' to change from -2 to -1. | ||||||||||||||||||||||
| // Ideally, we would want one of the two threads that is trapped | ||||||||||||||||||||||
| // inside 'block()' to return. | ||||||||||||||||||||||
| // Unfortunately, 'mycount' will now have the value '-1', and both | ||||||||||||||||||||||
| // of the two threads will conclude that they cannot return from 'blcck()'. | ||||||||||||||||||||||
| // So, the consistency criterion is violated: two threads blocked, count == -1 | ||||||||||||||||||||||
| // The essence of the homework is _either_ to diagnose this bug, or else | ||||||||||||||||||||||
| // to fix this bug (depending on the homework spec). | ||||||||||||||||||||||
| // If the homework is to fix the bug, then you must make it work | ||||||||||||||||||||||
| // for correctly for arbitrarily many threads. | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| // This is a partial implementation of producer-consumer. Fill in the rest. | ||
|
|
||
| // For debugging in GDB with threads: methodology 1 | ||
| // (gdb) info threads | ||
| // (gdb) thread 2 | ||
| // (gdb) # and so on for other threads | ||
| // (gdb) where # each thread has its own stack | ||
| // (gdb) frame 2 # to go to call frame 2 | ||
|
|
||
| // For debugging in GDB: methodology 2 | ||
| // (gdb) break consumer | ||
| // (gdb) run | ||
| // (gdb) print buf | ||
| // (gdb) next | ||
| // (gdb) print buf # and so on | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
| #include <assert.h> | ||
| #include <pthread.h> // Needed for pthread_mutex_lock(), etc. | ||
| #include "MCProgress.h" | ||
| // This many producer and this many consumer threads. | ||
| #define NUM_PROD_THREADS 3 | ||
| #define NUM_CONS_THREADS 3 | ||
|
|
||
| // You have to: | ||
| // 1. declare sem_t for your implementation | ||
| // 2. Add to 'main': sem_init, pthread_create, pthread_join | ||
| // 3. define push_buf() and take_from_buf() | ||
| // 4. link this with your semaphore.o (for your semaphore.c file), | ||
| // which should defines sem_init, sem_wait, sem_post. | ||
| // Note that 'partial_solution.c' is available for you to implement | ||
| // sem_init, sem_wait, sem_post well enough for 1 producer, 1 consumer. | ||
|
|
||
| struct sem { | ||
| int count; | ||
| pthread_mutex_t mutex; | ||
| }; | ||
| typedef struct sem sem_t; | ||
|
|
||
| int sem_init(sem_t *sem, int pshared, unsigned int value); | ||
| int sem_wait(sem_t *sem); | ||
| int sem_post(sem_t *sem); | ||
|
|
||
| sem_t sem_producer; // Should count number of empty slots available | ||
| sem_t sem_consumer; // Should count number of items in the buffer | ||
| // pthread_mutex_t mut_buf = PTHREAD_MUTEX_INITIALIZER; // Lock for anybody touching buf | ||
| pthread_mutex_t mut_buf; // Lock for anybody touching buf | ||
|
|
||
| pthread_t prod_thread[NUM_PROD_THREADS]; | ||
| pthread_t cons_thread[NUM_CONS_THREADS]; | ||
| void *producer(void *arg); | ||
| void *consumer(void *arg); | ||
|
|
||
| // ========================================= | ||
| // Implement circular buffer 'buf' | ||
| #define BUF_EMPTY_ITEM (-1) | ||
| #define BUF_NUM_ITEMS 2 | ||
| // #define TRACE | ||
| int buf[BUF_NUM_ITEMS]; | ||
| int push_idx = 0; | ||
| int take_idx = 0; | ||
| void initialize_buf() { | ||
| int i; | ||
| for (i = 0; i < BUF_NUM_ITEMS; i++) { | ||
| buf[i] = BUF_EMPTY_ITEM; | ||
| } | ||
| } | ||
| void push_buf(int work_item) { | ||
| assert(buf[push_idx] == BUF_EMPTY_ITEM); | ||
| buf[push_idx] = work_item; | ||
| #ifdef TRACE | ||
| fprintf(stderr, "PUSH: buf[%d] = %d\n", push_idx, work_item); | ||
| #endif | ||
| push_idx = (push_idx+1) % BUF_NUM_ITEMS; | ||
| } | ||
| int take_from_buf() { | ||
| int work_item = buf[take_idx]; | ||
| buf[take_idx] = BUF_EMPTY_ITEM; | ||
| #ifdef TRACE | ||
| fprintf(stderr, "TAKE: work_item:%d = buf[%d]\n", work_item, take_idx); | ||
| #endif | ||
| assert(work_item != BUF_EMPTY_ITEM); | ||
| take_idx = (take_idx+1) % BUF_NUM_ITEMS; | ||
| return work_item; | ||
| } | ||
|
|
||
| // ========================================= | ||
| // Implement producer-consumer, using buffer 'buf'. | ||
|
|
||
| int main() { | ||
| // ... uses pthread_create to start producer and consumer | ||
| // You must add that. | ||
| initialize_buf(); // Initialize entries in buffer to '-1' for error checking. | ||
| pthread_mutex_init(&(mut_buf), NULL); | ||
| sem_init(&sem_producer, 0, BUF_NUM_ITEMS); | ||
| sem_init(&sem_consumer, 0, 0); | ||
| int i; | ||
| for (i = 0; i < NUM_PROD_THREADS; i++) { | ||
| pthread_create(&prod_thread[i], NULL, producer, NULL); | ||
| } | ||
| for (i = 0; i < NUM_CONS_THREADS; i++) { | ||
| pthread_create(&cons_thread[i], NULL, consumer, NULL); | ||
| } | ||
| for (i = 0; i < NUM_PROD_THREADS; i++) { | ||
| pthread_join(prod_thread[i], NULL); | ||
| } | ||
| for (i = 0; i < NUM_CONS_THREADS; i++) { | ||
| pthread_join(cons_thread[i], NULL); | ||
| } | ||
| // WARNING: the primary thread runs main(). When main exits, the primary | ||
| // thread exits, unless you call 'pthread_join()' to | ||
| // have 'main' wait on the other threads before exiting. | ||
| while (1); // Don't let the primary thread exit | ||
| } | ||
|
|
||
| void *producer(void *arg) { | ||
| int work_item = 1; | ||
| while (1) { | ||
| // sleep( rand() % 5 ); | ||
| sem_wait(&sem_producer); // Wait for empty slots | ||
| pthread_mutex_lock(&mut_buf); | ||
| push_buf(work_item++); // inside critical section with mut_buf lock | ||
| MC_PROGRESS(); | ||
| pthread_mutex_unlock(&mut_buf); | ||
| sem_post(&sem_consumer); // Tell the consumer there's a new work item | ||
| } | ||
| } | ||
|
|
||
| // Exactly the same, but the inverse: | ||
| void *consumer(void *arg) { | ||
| while (1) { | ||
| int work_item; | ||
| // sleep( rand() % 5 ); | ||
| sem_wait(&sem_consumer); | ||
| pthread_mutex_lock(&mut_buf); | ||
| work_item = take_from_buf(); | ||
| MC_PROGRESS(); | ||
| pthread_mutex_unlock(&mut_buf); | ||
| sem_post(&sem_producer); | ||
|
|
||
| printf("%d ", work_item); | ||
| fflush(stdout); // Force printing now; don't wait for the newline | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject malformed
-lc<num>values instead of silently truncating them.The branch validates only the first suffix character, so
-lc12xstores12x; downstreamstrtoul(..., 10)then consumes the12prefix and silently applies the wrong limit. Validate that the entire suffix is numeric before callingsetenv, ideally sharing the same validator with the separated form.Proposed validation
else if (cur_arg[0][1] == 'l' && cur_arg[0][2] == 'c' && isdigit(cur_arg[0][3])) { + char *endptr; + strtol(cur_arg[0] + 3, &endptr, 10); + if (endptr[0] != '\0') { + fprintf(stderr, "%s: illegal value\n", + "--max-livelock-cycle-limit"); + exit(1); + } setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 3, 1); cur_arg++; }📝 Committable suggestion
🤖 Prompt for AI Agents