/* File: pth_linked_list_mult_mut.c * * Purpose: Implement a multi-threaded sorted linked list of * ints with ops insert, print, member, delete, free list. * This version uses one mutex per list node * * Compile: gcc -g -Wall -I. -o pth_linked_list_mult_mut * pth_linked_list_mult_mut.c my_rand.c -lpthread * Usage: ./pth_linked_list_mult_mut * Input: total number of keys inserted by main thread * total number of ops carried out * percent of ops that are searches and inserts (remaining ops * are deletes. * Output: Elapsed time to carry out the ops * * Notes: * 1. Repeated values are not allowed in the list * 2. DEBUG compile flag used. To get debug output compile with * -DDEBUG command line flag. * 3. Uses one mutex per node to control access to the list * 4. The random function is not threadsafe. So this program * uses a simple linear congruential generator. * 5. -DOUTPUT flag to gcc will show list before and after * threads have worked on it. * 6. Only Insert, Member and Delete use locks: Print and Free_List * should *not* be called when multiple threads are * accessing the list. */ #include #include #include #include "my_rand.h" #include "timer.h" /* Random ints are less than MAX_KEY */ const int MAX_KEY = 100000000; /* Return values of Advance_ptrs */ const int IN_LIST = 1; const int EMPTY_LIST = -1; const int END_OF_LIST = 0; /* Struct for list nodes */ struct list_node_s { int data; pthread_mutex_t mutex; struct list_node_s* next; }; /* Shared variables */ struct list_node_s* head = NULL; pthread_mutex_t head_mutex; int thread_count; int total_ops; double insert_percent; double search_percent; double delete_percent; pthread_mutex_t count_mutex; int member_total=0, insert_total=0, delete_total=0; /* Setup and cleanup */ void Usage(char* prog_name); void Get_input(int* inserts_in_main_p); /* Thread function */ void* Thread_work(void* rank); /* List operations */ void Init_ptrs(struct list_node_s** curr_pp, struct list_node_s** pred_pp); int Advance_ptrs(struct list_node_s** curr_pp, struct list_node_s** pred_pp); int Insert(int value); void Print(void); int Member(int value); int Delete(int value); void Free_list(void); int Is_empty(void); /*-----------------------------------------------------------------*/ int main(int argc, char* argv[]) { long i; int key, success, attempts; pthread_t* thread_handles; int inserts_in_main; unsigned seed = 1; double start, finish; if (argc != 2) Usage(argv[0]); thread_count = strtol(argv[1], NULL, 10); Get_input(&inserts_in_main); /* Try to insert inserts_in_main keys, but give up after */ /* 2*inserts_in_main attempts. */ i = attempts = 0; pthread_mutex_init(&head_mutex, NULL); while ( i < inserts_in_main && attempts < 2*inserts_in_main ) { key = my_rand(&seed) % MAX_KEY; success = Insert(key); attempts++; if (success) i++; } printf("Inserted %ld keys in empty list\n", i); # ifdef OUTPUT printf("Before starting threads, list = \n"); Print(); printf("\n"); # endif thread_handles = malloc(thread_count*sizeof(pthread_t)); pthread_mutex_init(&count_mutex, NULL); GET_TIME(start); for (i = 0; i < thread_count; i++) pthread_create(&thread_handles[i], NULL, Thread_work, (void*) i); for (i = 0; i < thread_count; i++) pthread_join(thread_handles[i], NULL); GET_TIME(finish); printf("Elapsed time = %e seconds\n", finish - start); printf("Total ops = %d\n", total_ops); printf("member ops = %d\n", member_total); printf("insert ops = %d\n", insert_total); printf("delete ops = %d\n", delete_total); # ifdef OUTPUT printf("After threads terminate, list = \n"); Print(); printf("\n"); # endif Free_list(); pthread_mutex_destroy(&head_mutex); pthread_mutex_destroy(&count_mutex); free(thread_handles); return 0; } /* main */ /*-----------------------------------------------------------------*/ void Usage(char* prog_name) { fprintf(stderr, "usage: %s \n", prog_name); exit(0); } /* Usage */ /*-----------------------------------------------------------------*/ void Get_input(int* inserts_in_main_p) { printf("How many keys should be inserted in the main thread?\n"); scanf("%d", inserts_in_main_p); printf("How many total ops should the threads execute?\n"); scanf("%d", &total_ops); printf("Percent of ops that should be searches? (between 0 and 1)\n"); scanf("%lf", &search_percent); printf("Percent of ops that should be inserts? (between 0 and 1)\n"); scanf("%lf", &insert_percent); delete_percent = 1.0 - (search_percent + insert_percent); } /* Get_input */ /*-----------------------------------------------------------------*/ /* Function: Init_ptrs * Purpose: Initialize pred and curr pointers before starting the * search carried out by Insert or Delete */ void Init_ptrs(struct list_node_s** curr_pp, struct list_node_s** pred_pp) { *pred_pp = NULL; pthread_mutex_lock(&head_mutex); *curr_pp = head; if (head != NULL) pthread_mutex_lock(&(head->mutex)); } /* Init_ptrs */ /*-----------------------------------------------------------------*/ /* Function: Advance_ptrs * Purpose: Advance the pair of pointers pred and curr during * Insert or Delete * Assumption: The calling thread already holds the locks to the * nodes referenced by curr_p and pred_p */ int Advance_ptrs(struct list_node_s** curr_pp, struct list_node_s** pred_pp) { int rv = IN_LIST; struct list_node_s* curr_p = *curr_pp; struct list_node_s* pred_p = *pred_pp; if (curr_p == NULL) if (pred_p == NULL) return EMPTY_LIST; else return END_OF_LIST; else { // *curr_pp != NULL if (curr_p->next != NULL) pthread_mutex_lock(&(curr_p->next->mutex)); else rv = END_OF_LIST; if (pred_p != NULL) pthread_mutex_unlock(&(pred_p->mutex)); else pthread_mutex_unlock(&head_mutex); *pred_pp = curr_p; *curr_pp = curr_p->next; return rv; } } /* Advance_ptrs */ /*-----------------------------------------------------------------*/ /* Insert value in correct numerical location into list */ /* If value is not in list, return 1, else return 0 */ int Insert(int value) { struct list_node_s* curr; struct list_node_s* pred; struct list_node_s* temp; int rv = 1; Init_ptrs(&curr, &pred); while (curr != NULL && curr->data < value) { Advance_ptrs(&curr, &pred); } if (curr == NULL || curr->data > value) { # ifdef DEBUG printf("Inserting %d\n", value); # endif temp = malloc(sizeof(struct list_node_s)); pthread_mutex_init(&(temp->mutex), NULL); temp->data = value; temp->next = curr; if (curr != NULL) pthread_mutex_unlock(&(curr->mutex)); if (pred == NULL) { // Inserting in head of list head = temp; pthread_mutex_unlock(&head_mutex); } else { pred->next = temp; pthread_mutex_unlock(&(pred->mutex)); } } else { /* value in list */ if (curr != NULL) pthread_mutex_unlock(&(curr->mutex)); if (pred != NULL) pthread_mutex_unlock(&(pred->mutex)); else pthread_mutex_unlock(&head_mutex); rv = 0; } return rv; } /* Insert */ /*-----------------------------------------------------------------*/ /* Doesn't use locks: cannot be run with the other threads */ void Print(void) { struct list_node_s* temp; printf("list = "); temp = head; while (temp != (struct list_node_s*) NULL) { printf("%d ", temp->data); temp = temp->next; } printf("\n"); } /* Print */ /*-----------------------------------------------------------------*/ int Member(int value) { struct list_node_s* temp; pthread_mutex_lock(&head_mutex); temp = head; while (temp != NULL && temp->data < value) { if (temp->next != NULL) pthread_mutex_lock(&(temp->next->mutex)); if (temp == head) pthread_mutex_unlock(&head_mutex); pthread_mutex_unlock(&(temp->mutex)); temp = temp->next; } if (temp == NULL || temp->data > value) { # ifdef DEBUG printf("%d is not in the list\n", value); # endif if (temp == head) pthread_mutex_unlock(&head_mutex); if (temp != NULL) pthread_mutex_unlock(&(temp->mutex)); return 0; } else { # ifdef DEBUG printf("%d is in the list\n", value); # endif if (temp == head) pthread_mutex_unlock(&head_mutex); pthread_mutex_unlock(&(temp->mutex)); return 1; } } /* Member */ /*-----------------------------------------------------------------*/ /* Deletes value from list */ /* If value is in list, return 1, else return 0 */ int Delete(int value) { struct list_node_s* curr; struct list_node_s* pred; int rv = 1; Init_ptrs(&curr, &pred); /* Find value */ while (curr != NULL && curr->data < value) { Advance_ptrs(&curr, &pred); } if (curr != NULL && curr->data == value) { if (pred == NULL) { /* first element in list */ head = curr->next; # ifdef DEBUG printf("Freeing %d\n", value); # endif pthread_mutex_unlock(&head_mutex); pthread_mutex_unlock(&(curr->mutex)); pthread_mutex_destroy(&(curr->mutex)); free(curr); } else { pred->next = curr->next; pthread_mutex_unlock(&(pred->mutex)); # ifdef DEBUG printf("Freeing %d\n", value); # endif pthread_mutex_unlock(&(curr->mutex)); pthread_mutex_destroy(&(curr->mutex)); free(curr); } } else { /* Not in list */ if (pred != NULL) pthread_mutex_unlock(&(pred->mutex)); if (curr != NULL) pthread_mutex_unlock(&(curr->mutex)); if (curr == head) pthread_mutex_unlock(&head_mutex); rv = 0; } return rv; } /* Delete */ /*-----------------------------------------------------------------*/ /* Doesn't use locks. Can only be run when no other threads are * accessing the list */ void Free_list(void) { struct list_node_s* current; struct list_node_s* following; if (Is_empty()) return; current = head; following = current->next; while (following != NULL) { # ifdef DEBUG printf("Freeing %d\n", current->data); # endif free(current); current = following; following = current->next; } # ifdef DEBUG printf("Freeing %d\n", current->data); # endif free(current); } /* Free_list */ /*-----------------------------------------------------------------*/ int Is_empty(void) { if (head == NULL) return 1; else return 0; } /* Is_empty */ /*-----------------------------------------------------------------*/ void* Thread_work(void* rank) { long my_rank = (long) rank; int i, val; double which_op; unsigned seed = my_rank + 1; int my_member=0, my_insert=0, my_delete=0; int ops_per_thread = total_ops/thread_count; for (i = 0; i < ops_per_thread; i++) { which_op = my_drand(&seed); val = my_rand(&seed) % MAX_KEY; if (which_op < search_percent) { # ifdef DEBUG printf("Thread %ld > Searching for %d\n", my_rank, val); # endif Member(val); my_member++; } else if (which_op < search_percent + insert_percent) { # ifdef DEBUG printf("Thread %ld > Attempting to insert %d\n", my_rank, val); # endif Insert(val); my_insert++; } else { /* delete */ # ifdef DEBUG printf("Thread %ld > Attempting to delete %d\n", my_rank, val); # endif Delete(val); my_delete++; } } /* for */ pthread_mutex_lock(&count_mutex); member_total += my_member; insert_total += my_insert; delete_total += my_delete; pthread_mutex_unlock(&count_mutex); return NULL; } /* Thread_work */