Oracle® Developer Studio 12.5:线程分析器用户指南

退出打印视图

更新时间: 2016 年 6 月
 
 

修复死锁和了解误报

一种消除潜在死锁和实际死锁的方法是使用一种令牌机制,该机制要求哲学家必须收到令牌后才能尝试进餐。可用令牌的数量必须少于餐桌前哲学家的人数。当哲学家收到令牌后,他可以根据餐桌规则尝试进餐。每位哲学家在进餐后归还令牌,然后重复该过程。以下伪代码显示了使用令牌机制时每位哲学家的逻辑。

   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 
      }

以下几节将详细介绍令牌机制的两种不同实现。

使用令牌控制哲学家

下面列出了使用令牌机制的哲学家就餐程序的修正版本。该解决方案结合了四个令牌,比就餐人数少一个,因此最多有四位哲学家可同时尝试进餐。该版本的程序称为 din_philo_fix1.c


提示  -  下载样例应用程序之后,可以从 OracleDeveloperStudio12.5-Samples/ThreadAnalyzer/din_philo 目录复制 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	}

尝试编译这一修正版本的哲学家就餐程序,并多次运行该程序。令牌机制限制了尝试使用筷子的就餐人数,从而可避免实际死锁和潜在死锁。

要进行编译,请使用以下命令:

% cc -g -o din_philo_fix1 din_philo_fix1.c

收集实验:

% collect -r deadlock -o din_philo_fix1.1.er din_philo_fix1 

误报的报告

即使是使用令牌机制,线程分析器也会在不存在死锁时报告该实现存在潜在死锁。这是一个误报。请参考以下包含潜在死锁详细信息的屏幕抓图。

图 10  误报的潜在死锁

image:线程分析器窗口的屏幕抓图,显示了线程 2 中的死锁。

选择循环链中的第一个线程(线程 2),然后单击 "Dual Source"(双源)视图,可以看到线程 2 在地址 0x216a8 持有锁的对应源代码位置,以及该线程在 0x216c0 请求锁的对应源代码位置。下图显示了对应于线程 2 的 "Dual Source"(双源)视图。

图 11  误报的潜在死锁的源代码

image:线程分析器 “Dual Source“(双源)视图的屏幕抓图,显示了潜在的死锁。

din_philo_fix1.c 中的 get_token() 函数使用 while 循环来同步线程。线程在成功获得令牌之前不会离开 while 循环(当 num_can_eat 大于 0 时可成功获得令牌)。while 循环将同时就餐的人数限制为 4。但是,线程分析器无法识别由 while 循环实现的同步。它认为所有五位哲学家同时尝试抓取筷子并进餐,因此报告了一个潜在死锁。下一节将详细说明如何使用线程分析器可识别的同步来限制同时就餐的人数。

另一种令牌机制

下面列出了令牌机制的另一种实现。该实现仍然使用四个令牌,所以最多有四个就餐者可同时尝试进餐。但是,该实现使用 sem_wait()sem_post() 信号例程限制就餐的哲学家人数。该版本的源文件称为 din_philo_fix2.c


提示  -  下载样例应用程序之后,可以从 OracleDeveloperStudio12.5-Samples/ThreadAnalyzer/din_philo 目录复制 din_philo_fix2.c 文件。

下面列出了详细的 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	}

这一新实现使用信号 num_can_eat_sem 来限制可同时就餐的哲学家人数。信号 num_can_eat_sem 初始化为 4,比哲学家人数少一个。尝试进餐前,哲学家调用 get_token(),后者又调用 sem_wait(&num_can_eat_sem)。调用 sem_wait() 会导致执行调用的哲学家进入等待状态,直到信号值变为正值,然后将信号值减 1。哲学家进餐完毕后,他将调用 return_token(),后者又调用 sem_post(&num_can_eat_sem)。调用 sem_post() 可将信号值加 1。线程分析器可识别对 sem_wait()sem_post() 的调用,并判定并非所有的哲学家都同时尝试进餐。


注 -  必须使用 –lrt 编译 din_philo_fix2.c 以链接相应的信号例程。

要编译 din_philo_fix2.c,请使用以下命令:

% cc -g -lrt -o din_philo_fix2 din_philo_fix2.c

如果多次运行程序 din_philo_fix2 的这一新实现,将会发现它每次都正常终止,不会挂起。

在这一新的二进制代码上创建实验:

% collect -r deadlock -o din_philo_fix2.1.er din_philo_fix2

您将发现线程分析器不报告 din_philo_fix2.1.er 实验中的任何实际死锁或潜在死锁,如下图所示。

图 12  未报告 din_philo_fix2.c 中的任何死锁

image:线程分析器窗口的屏幕抓图,显示没有任何死锁。

有关线程分析器可识别的线程和内存分配 API 的列表,请参见线程分析器可识别的 API