下面列出了使用令牌机制的哲学家就餐程序的修正版本。该解决方案结合了四个令牌,比就餐人数少一个,因此最多有四位哲学家可同时尝试进餐。该版本的程序称为 din_philo_fix1.c:
1 /* 2 * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All Rights Reserved. 3 * @(#)din_philo_fix1.c 1.3 (Oracle) 10/03/26 4 */ 5 6 #include <pthread.h> 7 #include <stdio.h> 8 #include <unistd.h> 9 #include <stdlib.h> 10 #include <errno.h> 11 #include <assert.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 void 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 pthread_mutex_t num_can_eat_lock; 31 int sleep_seconds = 0; 32 uint32_t num_can_eat = PHILOS - 1; 33 34 35 int 36 main (int argn, 37 char **argv) 38 { 39 int i; 40 41 pthread_mutex_init (&food_lock, NULL); 42 pthread_mutex_init (&num_can_eat_lock, NULL); 43 for (i = 0; i < PHILOS; i++) 44 pthread_mutex_init (&chopstick[i], NULL); 45 for (i = 0; i < PHILOS; i++) 46 pthread_create (&philo[i], NULL, philosopher, (void *)i); 47 for (i = 0; i < PHILOS; i++) 48 pthread_join (philo[i], NULL); 49 return 0; 50 } 51 52 void * 53 philosopher (void *num) 54 { 55 int id; 56 int i, left_chopstick, right_chopstick, f; 57 58 id = (int)num; 59 printf ("Philosopher %d is done thinking and now ready to eat.\n", id); 60 right_chopstick = id; 61 left_chopstick = id + 1; 62 63 /* Wrap around the chopsticks. */ 64 if (left_chopstick == PHILOS) 65 left_chopstick = 0; 66 67 while (f = food_on_table ()) { 68 get_token (); 69 70 grab_chopstick (id, right_chopstick, "right "); 71 grab_chopstick (id, left_chopstick, "left"); 72 73 printf ("Philosopher %d: eating.\n", id); 74 usleep (DELAY * (FOOD - f + 1)); 75 down_chopsticks (left_chopstick, right_chopstick); 76 77 return_token (); 78 } 79 80 printf ("Philosopher %d is done eating.\n", id); 81 return (NULL); 82 } 83 84 int 85 food_on_table () 86 { 87 static int food = FOOD; 88 int myfood; 89 90 pthread_mutex_lock (&food_lock); 91 if (food > 0) { 92 food--; 93 } 94 myfood = food; 95 pthread_mutex_unlock (&food_lock); 96 return myfood; 97 } 98 99 void 100 grab_chopstick (int phil, 101 int c, 102 char *hand) 103 { 104 pthread_mutex_lock (&chopstick[c]); 105 printf ("Philosopher %d: got %s chopstick %d\n", phil, hand, c); 106 } 107 108 109 110 void 111 down_chopsticks (int c1, 112 int c2) 113 { 114 pthread_mutex_unlock (&chopstick[c1]); 115 pthread_mutex_unlock (&chopstick[c2]); 116 } 117 118 119 void 120 get_token () 121 { 122 int successful = 0; 123 124 while (!successful) { 125 pthread_mutex_lock (&num_can_eat_lock); 126 if (num_can_eat > 0) { 127 num_can_eat--; 128 successful = 1; 129 } 130 else { 131 successful = 0; 132 } 133 pthread_mutex_unlock (&num_can_eat_lock); 134 } 135 } 136 137 void 138 return_token () 139 { 140 pthread_mutex_lock (&num_can_eat_lock); 141 num_can_eat++; 142 pthread_mutex_unlock (&num_can_eat_lock); 143 }
尝试编译这一修正版本的哲学家就餐程序,并多次运行该程序。令牌机制限制了尝试使用筷子的就餐人数,从而可避免实际死锁和潜在死锁。
要进行编译,请使用以下命令:
% cc -g -o din_philo_fix1 din_philo_fix1.c
收集实验:
% collect -r deadlock -o din_philo_fix1.1.er din_philo_fix1
即使是使用令牌机制,线程分析器也会在不存在死锁时报告该实现存在潜在死锁。这是一个误报。请参考以下包含潜在死锁详细信息的屏幕抓图。
图 3-5 误报的潜在死锁
选择循环链中的第一个线程(线程 2),然后单击 "Dual Source"(双源)视图,可以看到线程 2 在地址 0x216a8 持有锁的对应源代码位置,以及该线程在 0x216c0 请求锁的对应源代码位置。下图显示了对应于线程 2 的 "Dual Source"(双源)视图。
图 3-6 误报的潜在死锁的源代码
din_philo_fix1.c 中的 get_token() 函数使用 while 循环来同步线程。线程在成功获得令牌之前不会离开 while 循环(当 num_can_eat 大于 0 时可成功获得令牌)。while 循环将同时就餐的人数限制为 4。但是,线程分析器无法识别由 while 循环实现的同步。它认为所有五位哲学家同时尝试抓取筷子并进餐,因此报告了一个潜在死锁。下一节将详细说明如何使用线程分析器可识别的同步来限制同时就餐的人数。