One way to remove potential and actual deadlocks is to use a system of tokens so that a philosopher must receive a token before attempting to eat. The number of available tokens must be less than the number of philosophers at the table. After a philosopher receives a token, he can attempt to eat in accordance with the rules of the table. After eating, each philosopher returns the token and repeats the process. The following pseudo-code shows the logic for each philosopher when using the token system.
while (there is still food on the table) { get token grab right fork grab left fork eat some food put down left fork put down right fork return token }
The following sections detail two different implementations for the system of tokens.
The following listing shows the fixed version of the dining philosophers program that uses the token system. This solution incorporates four tokens, one less than the number of diners, so no more than four philosophers can attempt to eat at the same time. This version of the program is called din_philo_fix1.c:
1 /* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All Rights Reserved. 3 */ 4 5 #include <pthread.h> 6 #include <stdio.h> 7 #include <unistd.h> 8 #include <stdlib.h> 9 #include <errno.h> 10 #include <assert.h> 11 12 #ifdef __linux__ 13 #include <stdint.h> 14 #endif 15 16 #define PHILOS 5 17 #define DELAY 5000 18 #define FOOD 100 19 20 void *philosopher (void *id); 21 void grab_chopstick (int, 22 int, 23 char *); 24 void down_chopsticks (int, 25 int); 26 int food_on_table (); 27 int get_token (); 28 void return_token (); 29 30 pthread_mutex_t chopstick[PHILOS]; 31 pthread_t philo[PHILOS]; 32 pthread_mutex_t food_lock; 33 pthread_mutex_t num_can_eat_lock; 34 int sleep_seconds = 0; 35 uint32_t num_can_eat = PHILOS - 1; 36 37 38 int 39 main (int argn, 40 char **argv) 41 { 42 int i; 43 44 pthread_mutex_init (&food_lock, NULL); 45 pthread_mutex_init (&num_can_eat_lock, NULL); 46 for (i = 0; i < PHILOS; i++) 47 pthread_mutex_init (&chopstick[i], NULL); 48 for (i = 0; i < PHILOS; i++) 49 pthread_create (&philo[i], NULL, philosopher, (void *)i); 50 for (i = 0; i < PHILOS; i++) 51 pthread_join (philo[i], NULL); 52 return 0; 53 } 54 55 void * 56 philosopher (void *num) 57 { 58 int id; 59 int i, left_chopstick, right_chopstick, f; 60 61 id = (int)num; 62 printf ("Philosopher %d is done thinking and now ready to eat.\n", id); 63 right_chopstick = id; 64 left_chopstick = id + 1; 65 66 /* Wrap around the chopsticks. */ 67 if (left_chopstick == PHILOS) 68 left_chopstick = 0; 69 70 while (f = food_on_table ()) { 71 get_token (); 72 73 grab_chopstick (id, right_chopstick, "right "); 74 grab_chopstick (id, left_chopstick, "left"); 75 76 printf ("Philosopher %d: eating.\n", id); 77 usleep (DELAY * (FOOD - f + 1)); 78 down_chopsticks (left_chopstick, right_chopstick); 79 80 return_token (); 81 } 82 83 printf ("Philosopher %d is done eating.\n", id); 84 return (NULL); 85 } 86 87 int 88 food_on_table () 89 { 90 static int food = FOOD; 91 int myfood; 92 93 pthread_mutex_lock (&food_lock); 94 if (food > 0) { 95 food--; 96 } 97 myfood = food; 98 pthread_mutex_unlock (&food_lock); 99 return myfood; 100 } 101 102 void 103 grab_chopstick (int phil, 104 int c, 105 char *hand) 106 { 107 pthread_mutex_lock (&chopstick[c]); 108 printf ("Philosopher %d: got %s chopstick %d\n", phil, hand, c); 109 } 110 111 112 113 void 114 down_chopsticks (int c1, 115 int c2) 116 { 117 pthread_mutex_unlock (&chopstick[c1]); 118 pthread_mutex_unlock (&chopstick[c2]); 119 } 120 121 122 int 123 get_token () 124 { 125 int successful = 0; 126 127 while (!successful) { 128 pthread_mutex_lock (&num_can_eat_lock); 129 if (num_can_eat > 0) { 130 num_can_eat--; 131 successful = 1; 132 } 133 else { 134 successful = 0; 135 } 136 pthread_mutex_unlock (&num_can_eat_lock); 137 } 138 } 139 140 void 141 return_token () 142 { 143 pthread_mutex_lock (&num_can_eat_lock); 144 num_can_eat++; 145 pthread_mutex_unlock (&num_can_eat_lock); 146 }
Try compiling this fixed version of the dining philosophers program and running it several times. The system of tokens limits the number of diners attempting to use the chopsticks and thus avoids actual and potential deadlocks.
To compile, use the following command:
% cc -g -o din_philo_fix1 din_philo_fix1.c
To collect an experiment:
% collect -r deadlock -o din_philo_fix1.1.er din_philo_fix1
Even when using the system of tokens, Thread Analyzer reports a potential deadlock for this implementation when none exists. This is a false positive. Consider the following screen shot which details the potential deadlock.
Figure 10 False Positive Report of a Potential Deadlock
Select the first thread in the chain (Thread #2) and then click the Dual Source view to see the source code location in which Thread #2 held the lock at address 0x216a8, and where in the source code it requested the lock at address 0x216c0. The following figure shows the Dual Source view for Thread #2.
Figure 11 False Positive Potential Deadlock's Source
The get_token() function in din_philo_fix1.c uses a while loop to synchronize the threads. A thread will not leave the while loop until it successfully gets a token (this occurs when num_can_eat is greater than zero). The while loop limits the number of simultaneous diners to four. However, the synchronization implemented by the while loop is not recognized by Thread Analyzer. It assumes that all five philosophers attempt to grab the chopsticks and eat concurrently, so it reports a potential deadlock. The following section details how to limit the number of simultaneous diners by using synchronizations that Thread Analyzer recognizes.
The following listing shows an alternative implementation of the system of tokens. This implementation still uses four tokens, so no more than four diners attempt to eat at the same time. However, this implementation uses the sem_wait() and sem_post() semaphore routines to limit the number of eating philosophers. This version of the source file is called din_philo_fix2.c.
The following listing details din_philo_fix2.c:
1 /* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All Rights Reserved. 3 */ 4 5 #include <pthread.h> 6 #include <stdio.h> 7 #include <unistd.h> 8 #include <stdlib.h> 9 #include <errno.h> 10 #include <assert.h> 11 #include <semaphore.h> 12 13 #define PHILOS 5 14 #define DELAY 5000 15 #define FOOD 100 16 17 void *philosopher (void *id); 18 void grab_chopstick (int, 19 int, 20 char *); 21 void down_chopsticks (int, 22 int); 23 int food_on_table (); 24 int get_token (); 25 void return_token (); 26 27 pthread_mutex_t chopstick[PHILOS]; 28 pthread_t philo[PHILOS]; 29 pthread_mutex_t food_lock; 30 int sleep_seconds = 0; 31 sem_t num_can_eat_sem; 32 33 34 int 35 main (int argn, 36 char **argv) 37 { 38 int i; 39 40 pthread_mutex_init (&food_lock, NULL); 41 sem_init(&num_can_eat_sem, 0, PHILOS - 1); 42 for (i = 0; i < PHILOS; i++) 43 pthread_mutex_init (&chopstick[i], NULL); 44 for (i = 0; i < PHILOS; i++) 45 pthread_create (&philo[i], NULL, philosopher, (void *)i); 46 for (i = 0; i < PHILOS; i++) 47 pthread_join (philo[i], NULL); 48 return 0; 49 } 50 51 void * 52 philosopher (void *num) 53 { 54 int id; 55 int i, left_chopstick, right_chopstick, f; 56 57 id = (int)num; 58 printf ("Philosopher %d is done thinking and now ready to eat.\n", id); 59 right_chopstick = id; 60 left_chopstick = id + 1; 61 62 /* Wrap around the chopsticks. */ 63 if (left_chopstick == PHILOS) 64 left_chopstick = 0; 65 66 while (f = food_on_table ()) { 67 get_token (); 68 69 grab_chopstick (id, right_chopstick, "right "); 70 grab_chopstick (id, left_chopstick, "left"); 71 72 printf ("Philosopher %d: eating.\n", id); 73 usleep (DELAY * (FOOD - f + 1)); 74 down_chopsticks (left_chopstick, right_chopstick); 75 76 return_token (); 77 } 78 79 printf ("Philosopher %d is done eating.\n", id); 80 return (NULL); 81 } 82 83 int 84 food_on_table () 85 { 86 static int food = FOOD; 87 int myfood; 88 89 pthread_mutex_lock (&food_lock); 90 if (food > 0) { 91 food--; 92 } 93 myfood = food; 94 pthread_mutex_unlock (&food_lock); 95 return myfood; 96 } 97 98 void 99 grab_chopstick (int phil, 100 int c, 101 char *hand) 102 { 103 pthread_mutex_lock (&chopstick[c]); 104 printf ("Philosopher %d: got %s chopstick %d\n", phil, hand, c); 105 } 106 107 void 108 down_chopsticks (int c1, 109 int c2) 110 { 111 pthread_mutex_unlock (&chopstick[c1]); 112 pthread_mutex_unlock (&chopstick[c2]); 113 } 114 115 116 int 117 get_token () 118 { 119 sem_wait(&num_can_eat_sem); 120 } 121 122 void 123 return_token () 124 { 125 sem_post(&num_can_eat_sem); 126 }
This new implementation uses the semaphore num_can_eat_sem to limit the number of philosophers who can eat at the same time. The semaphore num_can_eat_sem is initialized to four, one less than the number of philosophers. Before attempting to eat, a philosopher calls get_token() which in turn calls sem_wait(&num_can_eat_sem). The call to sem_wait() causes the calling philosopher to wait until the semaphore's value is positive, then changes the semaphore's value by subtracting one from the value. When a philosopher is done eating, he calls return_token() which in turn calls sem_post(&num_can_eat_sem). The call to sem_post() changes the semaphore's value by adding one. Thread Analyzer recognizes the calls to sem_wait() and sem_post(), and determines that not all philosophers attempt to eat concurrently.
To compile din_philo_fix2.c, use the following command:
% cc -g -lrt -o din_philo_fix2 din_philo_fix2.c
If you run this new implementation of the program din_philo_fix2 several times, you will find that it terminates normally each time and does not hang.
To create an experiment on this new binary:
% collect -r deadlock -o din_philo_fix2.1.er din_philo_fix2
You will find that Thread Analyzer does not report any actual or potential deadlocks in the din_philo_fix2.1.er experiment, as the following figure shows.
Figure 12 Deadlocks Not Reported in din_philo_fix2.c
See APIs Recognized by Thread Analyzer for a listing of the threading and memory allocation APIs that Thread Analyzer recognizes.