typedef struct { int jobsize; int userid; int jobid; } Job; typedef struct { int nusers; int nprinters; int arrtime; int maxpages; int bufsize; int nevents; int starttime; int id; int event; Job *job; void *v; } Spq; extern void initialize_v(Spq *); /* When this is done, it will call function(s) as a continuation */ extern void submit_job(Spq *s, Job *j, void (*function)()); /* When this is done, it will call function(s) as a continuation, and s->job will be set to be the job. */ extern void get_print_job(Spq *s, void (*function)()); |
void user_ready_to_print(Spq *s); void user_job_submitted(Spq *s); void user_thread(Spq *s) { int sleeptime; if (s->event == s->nevents) return; sleeptime = random()%(s->arrtime*2) + 1; printf("%4d: user %2d/%03d: Sleeping for %2d seconds\n", time(0)-s->starttime, s->id, s->event, sleeptime); fflush(stdout); cbthread_sleep(sleeptime, user_ready_to_print, s); } void user_ready_to_print(Spq *s) { Job *j; j = (Job *) malloc(sizeof(Job)); j->jobsize = (random()%s->maxpages) + 1; j->userid = s->id; j->jobid = s->event; printf("%4d: user %2d/%03d: Submitting a job with size %d\n", time(0)-s->starttime, s->id, s->event, j->jobsize); fflush(stdout); submit_job(s, j, user_job_submitted); } void user_job_submitted(Spq *s) { s->event++; user_thread(s); } |
There are three user-thread procedures: user_thread(), user_ready_to_print() and user_job_submitted(). Since the first thing that a user does is sleep, this is done in user_thread(). Cbthread_sleep() is a blocking call, so the continuation user_ready_to_print() is called when the sleeping is over. That procedure generates a job and calls submit_job(), with user_job_submitted() as a continuation. The final continuation simply updates the event and repeats the user_thread().
The printer threads are broken up similarly.
The first solution is a standard solution to this problem -- add some information and pass that to the continuation. This code is in ps_cbt1.c. Specifically, I define a struct called Mycont, which includes the function() parameter, the Spq, a job and a state. Then, I implement submit_job() by allocating one of these Mycont's and calling real_submit_job() on it. real_submit_job() does all the work. It first calls P() on the slot semaphore, using itself as the continuation. When it is called a second time, it puts the job into the slot, deallocates the Mycont and calls the exit function on s.
Get_print_job() works in the exact same way. It's a little clunky, but it works.
#define START 0 #define GOT_JOB 1 #define GOT_SLOT 2 typedef struct { void (*func)(); Spq *s; Job *job; int state; } Mycont; void real_submit_job(Mycont *mc); void real_get_print_job(Mycont *mc); typedef struct { cbthread_gsem job_sem; cbthread_gsem slot_sem; Job **buffer; int head; int tail; } Info; void initialize_v(Spq *s) { Info *info; info = (Info *) malloc(sizeof(Info)); info->job_sem = cbthread_make_gsem(0); info->slot_sem = cbthread_make_gsem(s->bufsize); info->buffer = (Job **) malloc(sizeof(Job *)*s->bufsize); info->head = 0; info->tail = 0; s->v = (void *) info; } void submit_job(Spq *s, Job *j, void (*function)()) { Mycont *mc; mc = (Mycont *) malloc(sizeof(Mycont)); mc->func = function; mc->job = j; mc->s = s; mc->state = START; real_submit_job(mc); } void real_submit_job(Mycont *mc) { Info *info; Spq *s; void (*func)(); s = mc->s; info = (Info *) s->v; if (mc->state == START) { mc->state = GOT_SLOT; cbthread_gsem_P(info->slot_sem, real_submit_job, mc); } else if (mc->state == GOT_SLOT) { info->buffer[info->tail] = mc->job; info->tail = (info->tail + 1) % s->bufsize; cbthread_gsem_V(info->job_sem); func = mc->func; free(mc); (*func)(s); cbthread_exit(); } } void get_print_job(Spq *s, void (*function)()) { Mycont *mc; mc = (Mycont *) malloc(sizeof(Mycont)); mc->func = function; mc->s = s; mc->state = START; real_get_print_job(mc); } void real_get_print_job(Mycont *mc) { Spq *s; Info *info; void (*func)(); s = mc->s; info = (Info *) s->v; if (mc->state == START) { mc->state = GOT_JOB; cbthread_gsem_P(info->job_sem, real_get_print_job, mc); } else if (mc->state == GOT_JOB) { s->job = info->buffer[info->head]; info->head = (info->head + 1) % s->bufsize; cbthread_gsem_V(info->slot_sem); func = mc->func; free(mc); (*func)(s); cbthread_exit(); } } |
typedef void (*func)(); typedef struct { cbthread_gsem job_sem; cbthread_gsem slot_sem; Job **buffer; int head; int tail; func *user_funcs; Job **user_jobs; func *printer_funcs; } Info; void initialize_v(Spq *s) { Info *info; info = (Info *) malloc(sizeof(Info)); info->job_sem = cbthread_make_gsem(0); info->slot_sem = cbthread_make_gsem(s->bufsize); info->buffer = (Job **) malloc(sizeof(Job *)*s->bufsize); info->user_funcs = (func *) malloc(sizeof(func)*s->nusers); info->printer_funcs = (func *) malloc(sizeof(func)*s->nprinters); info->user_jobs = (Job **) malloc(sizeof(Job *)*s->nusers); info->head = 0; info->tail = 0; s->v = (void *) info; } |
I break up submit_job() into two procedures -- one before the P() call and one afterwards. The job and function that are parameters to submit_job() are stored in the user_jobs and user_funcs arrays, so that submit_job_slot_ready() may use them after the P() call unblocks. At the end of submit_job_slot_ready(), I call the user's function using cbthread_yield(). That's just to demonstrate it. I could have called the function directly as in Solution #1.
Get_print_job() is broken up similarly. I personally like the second solution better than the first, because I don't have to call malloc() and free() to help with control flow, but that's just my preference. I figured it would be good for y'all to see both.
void submit_job(Spq *s, Job *j, void (*function)()) { Info *info; info = (Info *) s->v; info->user_jobs[s->id] = j; info->user_funcs[s->id] = function; cbthread_gsem_P(info->slot_sem, submit_job_slot_ready, s); } void submit_job_slot_ready(Spq *s) { Info *info; info = (Info *) s->v; info->buffer[info->tail] = info->user_jobs[s->id]; info->tail = (info->tail + 1) % s->bufsize; cbthread_gsem_V(info->job_sem); cbthread_yield(info->user_funcs[s->id], s); } void get_print_job(Spq *s, void (*function)()) { Info *info; info = (Info *) s->v; info->printer_funcs[s->id] = function; cbthread_gsem_P(info->job_sem, job_ready, s); } void job_ready(Spq *s) { Info *info; info = (Info *) s->v; s->job = info->buffer[info->head]; info->head = (info->head + 1) % s->bufsize; cbthread_gsem_V(info->slot_sem); cbthread_yield(info->printer_funcs[s->id], s); } |
Ask yourself whether the program wakes up too many or too few threads in the various V() calls. Could you remove the if statements ahead of them? If so, would the program run incorrectly or more slowly (the answer is more slowly, but correctly).
typedef void (*func)(); typedef struct { cbthread_gsem user_sem; cbthread_gsem printer_sem; Job **buffer; int head; int tail; int njobs; func *user_funcs; Job **user_jobs; func *printer_funcs; } Info; void initialize_v(Spq *s) { Info *info; info = (Info *) malloc(sizeof(Info)); info->user_sem = cbthread_make_gsem(0); info->printer_sem = cbthread_make_gsem(0); info->buffer = (Job **) malloc(sizeof(Job *)*s->bufsize); info->user_funcs = (func *) malloc(sizeof(func)*s->nusers); info->printer_funcs = (func *) malloc(sizeof(func)*s->nprinters); info->user_jobs = (Job **) malloc(sizeof(Job *)*s->nusers); info->head = 0; info->tail = 0; info->njobs = 0; s->v = (void *) info; } void submit_job_cont(Spq *s) { Info *info; info = (Info *) s->v; if (info->njobs != s->bufsize) { info->buffer[info->tail] = info->user_jobs[s->id]; info->tail = (info->tail + 1) % s->bufsize; info->njobs++; if (cbthread_gsem_getval(info->printer_sem) < 0) cbthread_gsem_V(info->printer_sem); (*info->user_funcs[s->id])(s); cbthread_exit(); } else { cbthread_gsem_P(info->user_sem, submit_job_cont, s); } } void submit_job(Spq *s, Job *j, void (*function)()) { Info *info; info = (Info *) s->v; info->user_funcs[s->id] = function; info->user_jobs[s->id] = j; submit_job_cont(s); } void get_print_job_cont(Spq *s) { Info *info; info = (Info *) s->v; if (info->njobs == 0) { cbthread_gsem_P(info->printer_sem, get_print_job_cont, s); } else { s->job = info->buffer[info->head]; info->head = (info->head + 1) % s->bufsize; info->njobs--; if (cbthread_gsem_getval(info->user_sem) < 0) cbthread_gsem_V(info->user_sem); (*info->printer_funcs[s->id])(s); } } void get_print_job(Spq *s, void (*function)()) { Info *info; info = (Info *) s->v; info->printer_funcs[s->id] = function; get_print_job_cont(s); } |